- 本文講解Protobuf協議的語法,關於Probuf的C/C++編程可以參閱:https://blog.csdn.net/qq_41453285/article/details/106745639
一、Protobuf協議概述
-
protocolbuffer是google 的一種數據交換的格式,它獨立於語言,獨立於平臺。google 提供了多種語言的實現:java、c#、c++、go和 python,每一種實現都包含了相應語言的編譯器以及庫文件。由於它是一種二進制的格式,比使用xml進行數據交換快許多。可以把它用於分佈式應用之間的數據通信或者異構環境下的數據交換。作爲一種效率和兼容性都很優秀的二進制數據傳輸格式,可以用於諸如網絡傳輸、配置文件、數據存儲等諸多領域
-
據Google官方文檔介紹,現在Google內部已經有48162個消息類型定義在12183個proto文件中
-
本文介紹Protobuf 3.0的語法,和2.0語法有兼容性問題
二、消息類型
- 先來看一個非常簡單的例子。假設你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以採用如下的方式來定義消息類型的.proto文件了:
- 文件的第一行指定了你正在使用proto3語法:如果你沒有指定這個,編譯器會使用proto2。這個指定語法行必須是文件的非 空非註釋的第一個行
- SearchRequest消息格式有3個字段:在消息中承載的數據分別對應於每一個字段。其中每個字段都有一個名字和一種類型
syntax = "proto3";
//定義一個消息類型
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
字段類型
- 在上面的例子中,所有字段都是標量類型:兩個整型(page_number和result_per_page),一個string類型(query)
- 當然,你也可以爲字段指定其他的合成類型,包括枚舉(enumerations)或其他消息類型
分配標識號
- 在消息定義中,每個字段都有唯一的一個數字標識符。這些標識符是用來在消息的二進制格式中識別各個字段的, 一旦開始使用就不能夠再改變
- 例如在上面的案例中query的標識號爲1,page_number的標識號爲2,result_per_page的標識號爲3
- 相關語法:
- [1,15]之內的標識號在編碼的時候會佔用一個字節。[16,2047]之內的標識號則佔用2個字節。 所以應該爲那些頻繁出現的消息元素保留 [1,15]之內的標識號。切記:要爲將來有可能添加的、頻繁出現的標識號預留一些標識號
- 最小的標識號可以從1開始,最大到2^29-1,or536,870,911
- 不可以使用其中的[19000-19999](從 FieldDescriptor::kFirstReservedNumber到FieldDescriptor::kLastReservedNumber)的標識號, Protobuf協議實現中對這些進 行了預留。如果非要在.proto文件中使用這些預留標識號,編譯時就會報警
- 同樣你也不能使用早期保留的標識號
指定字段規則
- 所指定的消息字段修飾符必須是如下之一:
- singular:一個格式良好的消息應該有0個或者1個這種字段(但是不能超過1個)(默認爲singular,而且顯示寫上singular關鍵字會報錯,這個是Google的一個坑)
- repeated:在一個格式良好的消息中,這種字段可以重複任意多次(包括0次)。重複的值的順序會被保留。 在proto3中,repeated的標量域默認情況下使用packed
添加更多消息類型
- 在一個.proto文件中可以定義多個消息類型
- 例如,如果想定義與SearchResponse消息類型對應的回覆消息格式的話,你可以將它添加到相同的.proto文件中,如:
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } message SearchResponse { //... }
添加註釋
- 向.proto文件添加註釋,可以使用C/C++/java風格的雙斜槓(//) 語法格式,如:
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; //Which page number do we want? int32 result_per_page = 3; //Number of results to return per page }
保留標識符(Reserved)
- 如果你通過刪除或者註釋所有域,以後的用戶可以重用標識號當你重新更新類型的時候。如果你使用舊版本加載相同的.proto文 件這會導致嚴重的問題,包括數據損壞、隱私錯誤等等
- 現在有一種確保不會發生這種情況的方法就是指定保留標識符(and/or names,which can also cause issues for JSON serialization不明白什麼意思),protocol buffer的編譯器會警告未來嘗試使用這些域標識符的用戶
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
- 備註:不要在同一行reserved聲明中同時聲明域名字和標識號
三、標量數值類型
- “序列化消息時各種類型如何編碼”的更多細節(https://developers.google.com/protocol-buffers/docs/encoding?hl=zh-cn):
- 在java中,無符號32位和64位整型被表示成他們的整型對應形似,最高位被儲存在標誌位中
- 對於所有的情況,設定值會執行類型檢查以確保此值是有效
- 64位或者無符號32位整型在解碼時被表示成爲ilong,但是在設置時可以使用int型值設定,在所有的情況下,值必須符合其 設置其類型的要求
- python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str類型
- Integer在64位的機器上使用,string在32位機器上使用
默認值
- 當一個消息被解析的時候,如果被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應的域被設置位一個默認值
- 對於不同類型指定如下:
- 對於strings,默認是一個空string
- 對於bytes,默認是一個空的bytes
- 對於bools,默認是false
- 對於數值類型,默認是0
- 對於枚舉,默認是第一個定義的枚舉值,必須爲0
- 對於消息類型(message),域沒有被設置,確切的消息是根據語言確定的,詳見https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn
- 對於可重複域的默認值是空(通常情況下是對應語言中空列表)
- 注:對於標量消息域,一旦消息被解析,就無法判斷域釋放被設置爲默認值(例如,例如boolean值是否被設置爲false)還是根本沒有被設置。你應該在定義你的消息類型時非常注意。例如,比如你不應該定義boolean的默認值false作爲任何行爲的觸發方式。也應該注意如果一個標量消息域被設置爲標誌位,這個值不應該被序列化傳輸
- 查看generated code guide(https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn)選擇你的語言的默認值的工作細節
枚舉
- 當需要定義一個消息類型的時候,可能想爲一個字段指定某“預定義值序列”中的一個值。例如,假設要爲每一個SearchRequest 消息添加一個corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現這一點:通過向消息定義中添加一個枚舉(enum)並且爲每個可能的值定義一個常量就可以了
- 在下面的例子中,在消息格式中添加了一個叫做Corpus的枚舉類型(它含有所有可能的值),以及一個類型爲Corpus的字段:
message SearchRequest { stirng query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4; }
- ①每個枚舉類型必須將其第一個類型映射爲0,這是因爲:
- 必須有有一個0值,我們可以用這個0值作爲默認值
- 這個零值必須爲第一個元素,爲了兼容proto2語義,枚舉類的第一個值總是默認值
- ②你可以通過將不同的枚舉常量指定位相同的值。如果這樣做你需要將allow_alias設定位true,否則編譯器會在別名的地方產 生一個錯誤信息。例如:
enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. }
- ③枚舉常量必須在32位整型值的範圍內。因爲enum值是使用可變編碼方式的,對負數不夠高效,因此不推薦在enum中使用負 數。如上例所示,可以在 一個消息定義的內部或外部定義枚舉——這些枚舉可以在.proto文件中的任何消息定義裏重用。當然也可以在一個消息中聲明一個枚舉類型,而在另一個不同的消息中使用它——採用MessageType.EnumType的語法格式
- ④當對一個使用了枚舉的.proto文件運行protocol buffer編譯器的時候,生成的代碼中將有一個對應的enum(對Java或C++來 說),或者一個特殊的EnumDescriptor類(對Python來說),它被用來在運行時生成的類中創建一系列的整型值符號常量 (symbolic constants)
- ⑤在反序列化的過程中,無法識別的枚舉值會被保存在消息中,雖然這種表示方式需要依據所使用語言而定。在那些支持開放 枚舉類型超出指定範圍之外的語言中(例如C++和Go),爲識別的值會被表示成所支持的整型。在使用封閉枚舉類型的語言中(Java),使用枚舉中的一個類型來表示未識別的值,並且可以使用所支持整型來訪問。在其他情況下,如果解析的消息被序列號,未識別的值將保持原樣
- 關於如何在你的應用程序的消息中使用枚舉的更多信息,請查看所選擇的語言:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html
五、其他消息類型
- 你可以將其他消息類型用作字段類型。例如,假設在每一個SearchResponse消息中包含Result消息,此時可以在相同的.proto文件中定義一個Result消息類型,然後在SearchResponse消息中指定一個Result類型的字段,如:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
六、嵌套類型
- 你可以在其他消息類型中定義、使用消息類型
- 在下面的例子中,Result消息就定義在SearchResponse消息內,如:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
- 如果你想在它的父消息類型的外部重用這個消息類型,你需要以Parent.Type的形式使用它,如:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
七、 import
- 通過import引用其他.proto文件,類似於C語言的include
演示案例
- 例如下面是IM.BaseDefine.proto文件
syntax = "proto3"; package IM.BaseDefine; option optimize_for = LITE_RUNTIME; enum PhoneType{ PHONE_DEFAULT = 0x0; PHONE_HOME = 0x0001; // 家庭電話 PHONE_WORK = 0x0002; // 工作電話 }
- 下面是IM.Login.proto文件,其引用了上面的IM.BaseDefine.proto文件
syntax = "proto3"; package IM.Login; import "IM.BaseDefine.proto"; //引用IM.BaseDefine.proto option optimize_for = LITE_RUNTIME; message Phone{ string number = 1; IM.BaseDefine.PhoneType phone_type = 2; //使用IM.BaseDefine.proto中的枚舉類型 } message Book{ //... } message Person{ //... }
八、更新一個消息類型
- 如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。 不用擔心!更新消息而不破壞已有代碼是非常簡單的
- 在更新時只要記住以下的規則即可:
- 不要更改任何已有的字段的數值標識
- 如果你增加新的字段,使用舊格式的字段仍然可以被你新產生的代碼所解析。你應該記住這些元素的默認值這樣你的新代碼 就可以以適當的方式和舊代碼產生的數據交互。相似的,通過新代碼產生的消息也可以被舊代碼解析:只不過新的字段會被 忽視掉。注意,未被識別的字段會在反序列化的過程中丟棄掉,所以如果消息再被傳遞給新的代碼,新的字段依然是不可用 的(這和proto2中的行爲是不同的,在proto2中未定義的域依然會隨着消息被序列化)
- 非required的字段可以移除——只要它們的標識號在新的消息類型中不再使用(更好的做法可能是重命名那個字段,例如在 字段前添加“OBSOLETE_”前綴,那樣的話,使用的.proto文件的用戶將來就不會無意中重新使用了那些不該使用的標識 號)
- int32, uint32, int64, uint64,和bool是全部兼容的,這意味着可以將這些類型中的一個轉換爲另外一個,而不會破壞向前、 向 後的兼容性。如果解析出來的數字與對應的類型不相符,那麼結果就像在C++中對它進行了強制類型轉換一樣(例如,如果 把一個64位數字當作int32來 讀取,那麼它就會被截斷爲32位的數字)
- sint32和sint64是互相兼容的,但是它們與其他整數類型不兼容
- string和bytes是兼容的——只要bytes是有效的UTF8編碼
- 嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本
- fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的
- 枚舉類型與int32,uint32,int64和uint64相兼容(注意如果值不相兼容則會被截斷),然而在客戶端反序列化之後他們可能會有不同的處理方式,例如,未識別的proto3枚舉類型會被保留在消息中,但是他的表示方式會依照語言而定。int類型的字 段總會保留他們的
九、Any
- Any類型消息允許你在沒有指定他們的.proto定義的情況下使用消息作爲一個嵌套類型。一個Any類型包括一個可以被序列化bytes類型的任意消息,以及一個URL作爲一個全局標識符和解析消息類型
- 爲了使用Any類型,你需要導入import google/protobuf/any.proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
- 對於給定的消息類型的默認類型URL是type.googleapis.com/packagename.messagename
- 不同語言的實現會支持動態庫以線程安全的方式去幫助封裝或者解封裝Any值。例如在java中,Any類型會有特殊的pack()和 unpack()訪問器,在C++中會有PackFrom()和UnpackTo()方法
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()‐>PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
- 目前,用於Any類型的動態庫仍在開發之中
- 如果你已經很熟悉proto2(https://developers.google.com/protocol-buffers/docs/proto?hl=zh-cn)語法,使用Any替換拓展(https://developers.google.com/protocol-buffers/docs/proto?hl=zh-cn#extensions)
十、OneOf
- 如果你的消息中有很多可選字段, 並且同時至多一個字段會被設置, 你可以加強這個行爲,使用oneof特性節省內存
- Oneof字段就像可選字段, 除了它們會共享內存, 至多一個字段會被設置。 設置其中一個字段會清除其它字段。 你可以使用case()或者WhichOneof()方法檢查哪個oneof字段被設置, 看你使用什麼語言了
使用OneOf
- 爲了在.proto定義Oneof字段, 你需要在名字前面加上oneof關鍵字,比如下面例子的test_oneof:
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }
- 然後你可以增加oneof字段到oneof 定義中。你可以增加任意類型的字段,但是不能使用repeated關鍵字
- 在產生的代碼中,oneof字段擁有同樣的getters 和setters, 就像正常的可選字段一樣。也有一個特殊的方法來檢查到底那個字段 被設置。你可以在相應的語言API指南中找到oneof API介紹(https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn)
OneOf特性
- 設置oneof會自動清楚其它oneof字段的值。所以設置多次後,只有最後一次設置的字段有值
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
- 如果解析器遇到同一個oneof中有多個成員,只有最會一個會被解析成消息
- oneof不支持repeated
- 反射API對oneof 字段有效
- 如果使用C++,需確保代碼不會導致內存泄漏。下面的代碼會崩潰, 因爲sub_message已經通過set_name()刪除了
SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message‐>set_... // Crashes here
- 在C++中,如果你使用Swap()兩個oneof消息,每個消息,兩個消息將擁有對方的值。例如在下面的例子中,msg1會擁 有sub_message並且msg2會有name
SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
向後兼容性問題
- 當增加或者刪除oneof字段時一定要小心。如果檢查oneof的值返回None/NOT_SET,它意味着oneof字段沒有被賦值或者在一個不同的版本中賦值了。 你不會知道是哪種情況,因爲沒有辦法判斷如果未識別的字段是一個oneof字段
- Tage 重用問題:
- 將字段移入或移除oneof:在消息被序列號或者解析後,你也許會失去一些信息(有些字段也許會被清除)
- 刪除一個字段或者加入一個字段:在消息被序列號或者解析後,這也許會清除你現在設置的oneof字段
- 分離或者融合oneof:行爲與移動常規字段相似
十一、Map(映射)
- 如果你希望創建一個關聯映射,protocol buffer提供了一種快捷的語法:
- key_type可以是任意Integer或者string類型(所以,除了floating和bytes的任意標量類型都是可以的)
- value_type可以是任意類型
map<key_type, value_type> map_field = N;
- 例如,如果你希望創建一個project的映射,每個Projecct使用一個string作爲key,你可以像下面這樣定義:
- Map的字段可以是repeated
- 序列化後的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map
- 當爲.proto文件產生生成文本格式的時候,map會按照key的順序排序,數值化的key會按照數值排序
- 從序列化中解析或者融合時,如果有重複的key則後一個key不會被使用,當從文本格式中解析map時,如果存在重複的key
map<string, Project> projects = 3;
- 生成map的API現在對於所有proto3支持的語言都可用了,你可以從API指南(https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn)找到更多信息
向後兼容性問題
- map語法序列化後等同於如下內容,因此即使是不支持map語法的protocol buffer實現也是可以處理你的數據的:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
十二、包(package)
- 當然可以爲.proto文件新增一個可選的package聲明符,用來防止不同的消息類型有命名衝突
- 例如,如果下面是一個C++項目的.proto文件,那麼該.proto文件生成的代碼中會生成兩個命名空間,一個爲命名空間foo,一個爲命名空間bar,其中bar命名空間嵌套在foo命名空間中
//生成的C++代碼中會有一個foo命名空間和一個foo::bar命名空間
package foo.bar;
message Open { ... }
- 在其他的消息格式定義中可以使用“包名+消息名”的方式來定義域的類型。如:
message Foo {
...
required foo.bar.Open open = 1;
...
}
- 包的聲明符會根據使用語言的不同影響生成的代碼:
- 對於C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中
- 對於Java,包聲明符 會變爲java的一個包,除非在.proto文件中提供了一個明確有java_package
- 對於Python,這個包聲明符是被忽略的,因爲Python模塊是按照其在文件系統中的位置進行組織的
- 對於Go,包可以被用做Go包名稱,除非你顯式的提供一個option go_package在你的.proto文件中
- 對於Ruby,生成的類可以被包裝在內置的Ruby名稱空間中,轉換成Ruby所需的大小寫樣式 (首字母大寫;如果第一個符 號不是一個字母,則使用PB_前綴),例如Open會在Foo::Bar名稱空間中
- 對於javaNano包會使用Java包,除非你在你的文件中顯式的提供一個option java_package
- 對於C#包可以轉換爲PascalCase後作爲名稱空間,除非你在你的文件中顯式的提供一個option csharp_namespace,例 如,Open會在Foo.Bar名稱空間中
演示案例
- 例如下面是一個名爲IM.Login.proto的文件,其定義了package
- 當生成C++代碼(截取部分)之後,在代碼中會有如下的命名空間
包及名稱的解析
- Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每個包會被看作是其父類包的 內部類。當然對於 (foo.bar.Baz)這樣以“.”分隔的意味着是從最外圍開始的。
- ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。 對於不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規則
十三、定義服務
- 如果想要將消息類型用在RPC(遠程方法調用)系統中,可以在.proto文件中定義一個RPC服務接口,protocol buffer編譯器將會根據所選擇的不同語言生成服務接口代碼及存根
- 例如,想要定義一個RPC服務並具有一個方法,該方法能夠接收 SearchRequest並返回一個SearchResponse,此時可以在.proto文件中進行如下定義:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
- 最直觀的使用protocol buffer的RPC系統是gRPC一個由谷歌開發的語言和平臺中的開源的PRC系統,gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接爲您從.proto文件中產生相關的RPC代碼
- 如果你不想使用gRPC,也可以使用protocol buffer用於自己的RPC實現,你可以從proto2語言指南(https://developers.google.com/protocol-buffers/docs/proto?hl=zh-cn#services)中找到更多信息,還有一些第三方開發的PRC實現使用Protocol Buffer。參考第三方插件wiki(https://github.com/protocolbuffers/protobuf/blob/master/docs/third_party.md)查看這些實現的列表
十四、JSON映射
- Proto3 支持JSON的編碼規範,使他更容易在不同系統之間共享數據,在下表中逐個描述類型
- 如果JSON編碼的數據丟失或者其本身就是null,這個數據會在解析成protocol buffer的時候被表示成默認值。如果一個字段在 protocol buffer中表示爲默認值,體會在轉化成JSON的時候編碼的時候忽略掉以節省空間。具體實現可以提供在JSON編碼中可選的默認值
十五、選項
- 在定義.proto文件時能夠標註一系列的options。Options並不改變整個文件聲明的含義,但卻能夠影響特定環境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到
- 一些選項是文件級別的,意味着它可以作用於最外範圍,不包含在任何消息內部、enum或服務定義中。一些選項是消息級別的,意味着它可以用在消息定義的內部。當然有些選項可以作用在域、enum類型、enum值、服務類型及服務方法中。到目前爲止,並沒有一種有效的選項能作用於所有的類型
常用的選擇
- java_package (文件選項):這個選項表明生成java類所在的包。如果在.proto文件中沒有明確的聲明java_package,就採用默 認的包名。當然了,默認方式產生的 java包名並不是最好的方式,按照應用名稱倒序方式進行排序的。如果不需要產生java 代碼,則該選項將不起任何作用。如:
option java_package = "com.example.foo";
- java_outer_classname (文件選項):該選項表明想要生成Java類的名稱。如果在.proto文件中沒有明確的 java_outer_classname定義,生成的class名稱將會根據.proto文件的名稱採用駝峯式的命名方式進行生成。如 (foo_bar.proto生成的java類名爲FooBar.java),如果不生成java代碼,則該選項不起任何作用。如:
option java_outer_classname = "Ponycopter";
- optimize_for(文件選項):可以被設置爲 SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值將通過如下的方式影響C++及java 代碼的生成:
- SPEED (default):protocol buffer編譯器將通過在消息類型上執行序列化、語法分析及其他通用的操作。這種代碼是最優的。
- ODE_SIZE:protocol buffer編譯器將會產生最少量的類,通過共享或基於反射的代碼來實現序列化、語法分析及各種其 它操作。採用該方式產生的代碼將比SPEED要少得多, 但是操作要相對慢些。當然實現的類及其對外的API與SPEED 模式都是一樣的。這種方式經常用在一些包含大量的.proto文件而且並不盲目追求速度的 應用中。
- LITE_RUNTIME:protocol buffer編譯器依賴於運行時核心類庫來生成代碼(即採用libprotobuflite 替代libprotobuf)。這種 核心類庫由於忽略了一 些描述符及反射,要比全類庫小得多。這種模式經常在移動手機平臺應用多一些。編譯器採用該模式產生的方法實現與SPEED模式不相上下,產生的類通過實現MessageLite接口,但它僅僅是Messager接口的一 個子集
option optimize_for = CODE_SIZE;
- cc_enable_arenas(文件選項):對於C++產生的代碼啓用arena allocation(https://developers.google.com/protocol-buffers/docs/reference/arenas?hl=zh-cn)
- objc_class_prefix(文件選項):設置ObjectiveC類的前綴,添加到所有ObjectiveC從此.proto文件產生的類和枚舉類型。沒 有默認值,所使用的前綴應該是蘋果推薦的35個大寫字符,注意2個字節的前綴是蘋果所保留的。
- deprecated(字段選項):如果設置爲true則表示該字段已經被廢棄,並且不應該在新的代碼中使用。在大多數語言中沒有實際 的意義。在java中,這回變成@Deprecated註釋,在未來,其他語言的代碼生成器也許會在字標識符中產生廢棄註釋,廢棄 註釋會在編譯器嘗試使用該字段時發出警告。如果字段沒有被使用你也不希望有新用戶使用它,嘗試使用保留語句替換字段聲明
int32 old_field = 6 [deprecated=true];
自定義選項
- ProtocolBuffers允許自定義並使用選項。該功能應該屬於一個高級特性,對於大部分人是用不到的。如果你的確希望創建自己的 選項,請參看 Proto2 Language Guide(https://developers.google.com/protocol-buffers/docs/proto?hl=zh-cn#customoptions)。注意創建自定義選項使用了拓展,拓展只在proto3中可用。
十六、從.proto文件生成了什麼?
- 當用protocol buffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼可以操作在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息
- 例如:
- 對C++來說,編譯器會爲每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應的類
- 對Java來說,編譯器爲每一個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來創建消息類接口 的)
- 對Python來說,有點不太一樣——Python編譯器爲.proto文件中的每個消息類型生成一個含有靜態描述符的模塊,,該模塊 與一個元類(metaclass)在運行時(runtime)被用來創建所需的Python數據訪問類
- 對go來說,編譯器會位每個消息類型生成了一個.pd.go文件
- 對於Ruby來說,編譯器會爲每個消息類型生成了一個.rb文件
- javaNano來說,編譯器輸出類似域java但是沒有Builder類
- 對於ObjectiveC來說,編譯器會爲每個消息類型生成了一個pbobjc.h文件和pbobjcm文件,.proto文件中的每一個消息有一個 對應的類
- 對於C#來說,編譯器會爲每個消息類型生成了一個.cs文件,.proto文件中的每一個消息有一個對應的類
- 你可以從如下的文檔鏈接中獲取每種語言更多API:https://developers.google.com/protocol-buffers/docs/reference/overview?hl=zh-cn