Protobuf協議特色分析php
首先來看一個簡單的例子,定義一個搜索請求的消息格式,每一個消息包含一個請求字符串,你感興趣的頁數和每頁的結果數。下面是在.proto
文件中定義的消息。css
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; }
SearchRequest
消息定義了3個特殊的字段(名字/值 對)對應着我須要的的消息內容。每一個字段有一個名字和類型。java
在上面的例子中,全部的字段都是標量類型 : 兩個整形(page_number result_per_page
)和一個字符串query
。 固然你也可使用其餘組合類型,好比枚舉或者其餘 消息類型。python
如你所見,消息中的每個字段都被定義了一個獨一無二的數字標籤。這個標籤是用來在二進制的消息格式中區分字段的,一旦你的消息開始被使用,這些標籤就不該該在被修改了。注意 1 到 15 標籤在編碼的時候僅佔用1 byte ,16 - 2047 佔用 2 byte 。所以你應該將 1 - 15 標籤保留給最常常被使用的消息元素。另外爲將來可能添加的經常使用元素預留位子。
你能定義的最小的標籤是1, 最大是 2的29次方 -1 , 另外 19000 到 19999 (FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber
) 也不能用。他們是protobuf 的編譯預留標籤。另外你也不能使用被 reserved
的標籤。c++
消息是字段必須是下面的一種編程
required
格式正確的消息必須有一個這個字段。optional
格式正確的消息能夠有一個或者零個這樣的消息。repeated
這個字段能夠有任意多個。字段值的順序被保留。因爲歷史緣由, repeated
字段的標量編碼效率沒有應有的效率高,新的代碼可使用[packet=true]
來得到更高效的編碼, 好比 :json
repeated int32 samples = 4 [packet=true]
Required 字段意味着永久,當你要標記一個字段爲required 的時候你必須很是當心 —– 若是某個時刻你想要再也不使用這個字段,當你把它改爲optional的時候就會出問題 : 使用舊的協議的人會由於認爲這個字段缺失而認爲消息不完整,進而拒收或者丟棄這個消息。谷歌的一些工程師得出這樣的結論:使用required
形成的傷害比他們的好處多,他們更傾向於使用optional
的和repeated
的。然而,這種觀點不是絕對的。安全
多個消息類型能夠在一個.proto
文件中定義。當你定義多個相關聯的消息的時候就用的上了 —— 好比我要定義一個返回消息格式來回應SearchRequest
消息,那麼我在同一個文件中 :bash
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } message SearchResponse { //。。。
添加註釋
在.proto
文件中添加註釋,使用C/C++風格的 //
語法服務器
message SearchRequest { required string query = 1; optional int32 page_number = 2;// Which page number do we want? optional int32 result_per_page = 3;// Number of results to return per page. }
當你在某次更新消息中屏蔽或者刪除了一個字段的話,將來的使用着可能在他們的更新中重用這個標籤數字來標記他們本身的字段。而後當他們加載舊的消息的時候就會出現不少問題,包括數據衝突,隱藏的bug等等。指定這個字段的標籤數字(或者名字,名字可能在序列化爲JSON的時候可能衝突)標記爲reserved
來保證他們不會再次被使用。若是之後的人試用的話protobuf編譯器會提示出錯。
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
注意一個reserved字段不能既有標籤數字又有名字。
.proto
文件最終生成什麼當你使用protoc
來編譯一個.proto
文件的時候,編譯器將利用你在文件中定義的類型生成你打算使用的語言的代碼文件。生成的代碼包括getting setting
接口和序列化,反序列化接口。
.proto
文件生成一個.h
和一個.cc
文件。 每一個消息生成一個class。.java
文件,外加一個特殊的Builder
類來生成消息實例。.pb.go
文件。proto | Note | C++ | Java | Python | Go |
---|---|---|---|---|---|
float | float | float | float | *float32 | |
double | double | double | float | *float64 | |
int32 | 變長編碼. 編碼負數效率底下– 打算使用負數的話請使用 sint32. | int32 | int | int | *int32 |
int64 | 變長編碼. 編碼負數效率底下– 打算使用負數的話請使用 sint64. | int64 | long | int/long | *int64 |
uint32 | 變長編碼. | uint32 | int | int/long | *uint32 |
uint64 | 變長編碼. | uint64 | long | int/long | *uint64 |
sint32 | U變長編碼. 數值有符號,負數編碼效率高於int32 | int32 | int | int | *int32 |
sint64 | U變長編碼. 數值有符號,負數編碼效率高於int64 | int64 | long | int/long | *int64 |
fixed32 | 固定4byte, 若是數值常常大於2的28次方的話效率高於uint32. | uint32 | int | int | *uint32 |
fixed64 | 固定8byte, 若是數值常常大於2的56次方的話效率高於uint64. | uint64 | long | int/long | *uint64 |
sfixed32 | 固定4byte. | int32 | int | int | *int32 |
sfixed64 | 固定8byte. | int64 | long | int/long | *int64 |
bool | bool | boolean | bool | *bool | |
string | 字符串內容應該是 UTF-8 編碼或者7-bit ASCII 文本. | string | String | str/unicode | *string |
bytes | 任意二進制數據. | string | ByteString | str | []byte |
optional
字段和默認初始值按照上面提到的,元素能夠被標記爲optional
的。一個正確格式的消息能夠有也能夠沒有包含這個可選的字段。再解析消息的時候,若是個可選的字段沒有被設置,那麼他的值就會被設置成默認值。默認值能夠做爲消息描述的一不部分 :
optional int32 result_per_page = 3 [default = 10];
若是沒有明確指明默認值,那麼這個字段的值就是這個字段的類型默認值。好比 : 字符串的默認值就是空串。數字類型的默認值就是0。枚舉類型的默認值是枚舉定義表的第一個值,這意味着枚舉的第一個值須要被格外注意。
當你定義一個消息的時候,你可能但願它其中的某個字段必定是預先定義好的一組值中的一個。你如說我要在SearchRequest
中添加corpus
字段。它只能是 UNIVERSAL, WEB , IMAGES , LOCAL, NEWS ,PRODUCTS, 或者 VIDEO
。你能夠很簡單的在你的消息中定義一個枚舉而且定義corpus
字段爲枚舉類型,若是這個字段給出了一個再也不枚舉中的值,那麼解析器就會把它看成一個未知的字段。
1 message SearchRequest { 2 required string query = 1; 3 optional int32 page_number = 2; 4 optional int32 result_per_page = 3 [default = 10]; 5 enum Corpus { 6 UNIVERSAL = 0; 7 WEB = 1; 8 IMAGES = 2; 9 LOCAL = 3; 10 NEWS = 4; 11 PRODUCTS = 5; 12 VIDEO = 6; 13 } 14 optional Corpus corpus = 4 [default = UNIVERSAL]; 15 }
只須要將相同的值賦值給不一樣的枚舉項名字,你就在枚舉中你能夠定義別名 。固然你得先將allow_alias
選項設置爲true
, 不然編譯器遇到別名的時候就報錯。
1 enum EnumAllowingAlias { 2 option allow_alias = true; 3 UNKNOWN = 0; 4 STARTED = 1; 5 RUNNING = 1; 6 } 7 enum EnumNotAllowingAlias { 8 UNKNOWN = 0; 9 STARTED = 1; 10 // RUNNING = 1; //取消這一行的屏蔽的話,編譯器報錯。 11 }
枚舉常數必須是一個32爲的整數。因爲枚舉值在通信的時候使用變長編碼,因此負數的效率很低,不推薦使用。你能夠在(像上面這樣)在一個消息內定義枚舉,也能夠在消息外定義 —– 這樣枚舉就在全文件可見了。若是你想要使用在消息內定義的枚舉的話,使用語法 MessageType.EnumType
。
在你編譯帶有枚舉的.proto
文件的時候,若是生成的是C++或者Java代碼, 那麼生成的代碼中會有對應的枚舉。
你可使用其餘的消息類型做爲字段的類型。好比咱們打算在SearchResponse
消息中包含一個Result
類型的消息 :
message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; }
在上面的例子中, Result
消息類型是和SearchResponse
定義在同一個文件中,若是你想使用的消息類型已經在另外一個.proto
文件中定義的話怎麼辦 ?
只要你導入一個文件就可使用這個文件內定義的消息。在你的文件頭部加上這樣的語句來導入其餘文件: import "myproject/other_protos.proto";
默認狀況下你只能使用直接導入的文件中的定義。然而有的時候你須要將一個文件從一個路徑移動到另外一個路徑的時候,與其將全部的引用這個文件的地方都更新到新的路徑,不如在原來的路徑上留下一個假的文件,使用import public
來指向新的路徑。import public
語句能夠將它導入的文件簡介傳遞給導入本文減的文件。好比 :
// new.proto // 新的定義都在這裏
// old.proto // 其餘的文件其實導入的都是這個文件 import public "new.proto"; import "other.proto";
// client.proto import "old.proto"; // 你可使用 old.proto 和 new.proto 的定義, 可是不能使用other.proto的定義
在命令行中試用-I/--proto_path
來指定一系列的編譯器搜索路徑,若是這個參數沒有被設置,那麼默認在命令執行的路徑查找。一般狀況下使用-I/--proto_path
來指定到你項目的根目錄,而後使用完整的路徑來導入所需的文件。
你能夠將proto3的消息類型導入並在proto2的消息中使用,反之亦然。不過proto2的枚舉不能在proto3中使用。
你能夠在一個消息中定義並使用其餘消息類型,好比下面的例子 —— Result
消息是在SearchResponse
中定義的 :
1 message SearchResponse { 2 message Result { 3 required string url = 1; 4 optional string title = 2; 5 repeated string snippets = 3; 6 } 7 repeated Result result = 1; 8 }
若是你打算在這個消息的父消息以外重用這個消息的話,你能夠這樣引用它 : Parent.Type
message SomeOtherMessage { optional SearchResponse.Result result = 1; }
你想嵌套多深就嵌套多深,沒有限制 :
1 message Outer { // Level 0 2 message MiddleAA { // Level 1 3 message Inner { // Level 2 4 required int64 ival = 1; 5 optional bool booly = 2; 6 } 7 } 8 message MiddleBB { // Level 1 9 message Inner { // Level 2 10 required int32 ival = 1; 11 optional bool booly = 2; 12 } 13 } 14 }
注意這是一個被廢棄的特性,若是你建立一個新的消息的話,不要使用這個,請直接使用內嵌消息。
Groups是另外的一種在你的消息中內嵌信息的方式。例如 :
1 message SearchResponse { 2 repeated group Result = 1 { 3 required string url = 2; 4 optional string title = 3; 5 repeated string snippets = 4; 6 } 7 }
Group其實將內嵌消息的定義和字段聲明合併在一塊兒了。在你的生成代碼中,你會發現這個消息有一個Result類型的result字段(字段名字自動小寫來防止衝突)。 所以這個例子和上面的第一個內嵌的例子是等價的。除了這個消息的通信格式不大同樣外。
若是一個現有的消息類型再也不知足你的需求,好比你須要額外的字段,可是你仍然但願兼容舊代碼生成的消息的話,不要擔憂! 在不破壞現有代碼的前提下更新消息是很簡單的。請銘記下面的規則 :
optional
或者repeated
。因爲任何required
字段都沒有丟失,這意味着你的舊代碼序列化的消息可以被新代碼解析經過。你應該給新的字段設置合理的默認值,這樣新的代碼能夠合適解析使用舊的消息。一樣的,新的代碼產生的消息包也能夠被舊的代碼解析經過,舊的代碼在解析的時候會忽略新的字段。不過新的字段並無被丟棄,若是這個消息在舊的代碼中再次被序列化,這些未知的字段還會在裏面 —— 這樣這些消息被傳遞迴新的代碼的時候,解析仍然有效。required
字段能夠被移除,可是對應的數字標籤不能被重用。或許你能夠經過重命名這個字段,加上前綴OBSOLETE_
來表示廢棄。或者你能夠標記reserverd
。這樣你將來就不會不當心重用這些字段了。required
字段能夠被轉化擴展字段,反之亦然。int32, uint32, int64, uint64, 和 bool
這些類型是兼容的 —— 這意味着你能夠將一個字段的類型從其中的一種轉化爲另外一種,不會打破向前向後兼容! 若是通訊的時候傳輸的數字不符合對應類型的那麼你會獲得和C++中強制類型轉化同樣的效果(64bit數字會被截斷)。sint32 sint64
相互兼容,可是不和其餘的數字類型兼容。string bytes
相互兼容 ,前提是二進制內容是有效的UTF-8 。optional repeated
是兼容的。當給定的輸入字段是repeated
的時候,若是接收方期待的是一個optional
的字段的話,對與原始類型的字段,他會取最後一個值,對於消息類型的字段,他會將全部的輸入合併起來。int32, uint32, int64, and uint64
在傳輸格式中相互兼容(注意若是不合適會被 截斷),可是接收方在發序列化的時候處理他們可不大同樣。請注意: 反序列化的時候不正確的枚舉數字會被丟棄,這樣這個字段的has_xxx
接口就返回false
而且get_xxx
接口返回枚舉的第一個值。不過若是是一個整形字段的話,這個數值會一致保留。因此當你打算把一個整形更新爲枚舉的時候,請務必注意整數的值不要超出接收方枚舉的值。extensions
讓你定義一段可用的數字標籤來供第三方擴展你的消息。其餘人能夠在他們本身的文件裏面使用這些標籤數字來擴展你的下消息(無需修改你的消息文件)。 舉個例子:
message Foo { //,,, extensions 100 to 199; }
這意味着Foo
消息在[ 100 , 199 ]區間的標籤數字被保留作擴展使用。其餘的使用者能夠在他們本身的文件中導入你的文件,而後在他們本身的文件中給你的消息添加新的字段 :
extend Foo { optional int32 bar = 126; }
這樣就意味着Foo
消息如今有一個叫作bar
的int32
字段了。在編碼的時候,通信格式和使用者定義的新的消息同樣。不過你的程序訪問擴展字段的方式和訪問常規字段的方式不太同樣, 這裏以C++代碼爲例 :
Foo foo; foo.SetExtension(bar, 15);
相似的,Foo
類有如下接口HasExtension(), ClearExtension(), GetExtension(), MutableExtension(), and AddExtension()
。
注意擴展字段能夠是除了oneof
或者map
外的其餘任何類型,包括消息類型。
你能夠在其餘類型的做用域內定義擴展字段 :
message Baz { extend Foo { optional int32 bar = 126; } //。。。 }
在這種狀況下,擴展的字段以下訪問 ( C++ )
Foo foo; foo.SetExtension(Baz::bar, 15);
這裏有一個很常見的疑惑 : 在一個消息類型內定義另外一個類型的擴展並不會致使被擴展消息類型和包含類型的任何關係。實際上,在上面的例子中,Baz類不是Foo類的子類。上面僅僅意味着bar
這個變量其實是Baz
的一個static變量,僅此而已。
一個常規的使用方法是當咱們要擴展一個類型的字段的時候,將它寫在這個類型裏面, 好比我要擴展一個Baz類型的Foo字段的時候 :
message Baz { extend Foo { optional Baz foo_ext = 127; } ... }
然而,這並非必要的。你徹底能夠這樣作 :
message Baz { ... } // This can even be in a different file. extend Foo { optional Baz foo_baz_ext = 127; }
事實上這個語法是用來避免疑惑的。正如上面提到的,嵌套語法常常會不熟悉擴展的人被誤覺得是子類。
重要的是,要確保兩個使用者不會向同一個消息內擴展同一個數字的字段。不然若是類型剛好不兼容的話數據就混亂了。你須要爲你的項目定義合適的擴展數字來避免這種事。
若是你打算使用一些很是大的數字來做爲你的擴展的話,你可讓你的擴展字段區間一直到最大值,你能夠max
關鍵字 :
message Foo { extensions 1000 to max; }
max 是 2的29次方 - 1, 536,870,911.
一樣的你不能使用19000-19999 。 你能夠定義擴展空間包含他們,不過當你定義擴展字段的時候不能真的使用這些數字。
若是你的消息中有不少可選字段,而同一個時刻最多僅有其中的一個字段被設置的話,你可使用oneof
來強化這個特性而且節約存儲空間。 oneof
字段相似optional
字段只不過oneof
裏面全部的字段共享內存,並且統一時刻只有一個字段能夠被設着。設置其中任意一個字段都自動清理其餘字段。在你的代碼中,你可使用case()或者 WhichOneOf()
接口來查看究竟是哪一個字段被設置了。
使用Oneof特性你只須要在oneof
關鍵字後面加上它的名字就行 :
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }
你能夠在oneof
中使用oneof
, 你可使用任何類型的字段,可是你不能使用required, optional, 或者 repeated
關鍵字。
在你的代碼中,oneof內的字段和其餘常規字段有同樣的getter setter 接口。你還能夠經過接口(取決於你的語言)判斷哪一個字段被設置。
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); //清理name字段. CHECK(!message.has_name());
set_name
的時候sub_message
字段已經被清理了。SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
Swap()
接口的話,每一個消息會帶有對方的oneof字段。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字段。通信中可沒有辦法告訴你兩個版本的oneof到底哪裏不同了。
重用的注意事項:
若是你打算在你的數據結構中建立一個關聯表的話,咱們提供了很方便的語法:
map<key_type, value_type> map_field = N;
這裏key_type能夠是任意整形或者字符串。而value_tpye 能夠是任意類型。
舉個例子,若是你打算建立一個Project表,每一個Project關聯到一個字符串上的話 :
map<string, Project> projects = 3;
如今生成Map的API對於全部支持proto2的語言均可用了。
repeated, optional, 或者 required
.在通信中,map等價與下面的定義, 這樣不支持Map的版本也能夠解析你的消息:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
爲了防止不一樣消息之間的命名衝突,你能夠對特定的.proto
文件提指定packet 名字 。
package foo.bar; message Open { ... }
在定義你的消息字段類型的時候你能夠指定包的名字:
message Foo { ... required foo.bar.Open open = 1; ... }
包名字的實現取決於你工做的具體編程語言:
java_package
,不然這個包名字就是Java的包名字。protobuf的名字解析方式和C++很像。首先是最裏面的做用域被搜索,而後是外面的一層。。。 沒一個包都從他本身到它的父輩。可是若是前面有.
號的話就(好比foo.bar.Baz
)意味着從最外面開始。
protobuf 編譯器經過全部導入.proto
文件來解析全部的名字。代碼生成器爲每一個語言生成對應的合適的類型。
若是打算將你的消息配合一個RPC(Remote Procedure Call 遠程調用)系統聯合使用的話,你能夠在.proto
文件中定義一個RPC 服務接口而後protobuf就會給你生成一個服務接口和其餘必要代碼。好比你打算定義一個遠程調用,接收SearchRequest返回SearchResponse, 那麼你在你的文件中這樣定義 :
service SearchService { rpc Search (SearchRequest) returns (SearchResponse); }
默認狀況下,編譯器給你生成一個純虛接口名叫SearchRequest
和一個對應的樁實現。這個樁實現直接調用RpcChannel,這個是你本身實現的具體RPC代碼。好比你打算實現一個RpcChannel來序列化消息而且使用HTTP發送。換句話說,生成的代碼提供了一個基於你的RPC的類型的安全的協議接口實現,它 不須要知曉你的PRC 的任何實現細節。所以最後你的代碼大致是這樣的 :
1 using google::protobuf; 2 3 protobuf::RpcChannel* channel; 4 protobuf::RpcController* controller; 5 SearchService* service; 6 SearchRequest request; 7 SearchResponse response; 8 9 void DoSearch() { 10 // 你本身提供MyRpcChannel和MyRpcController兩個類,這兩個類分別實現了純虛接口 11 // s protobuf::RpcChannel 和protobuf::RpcController. 12 channel = new MyRpcChannel("somehost.example.com:1234"); 13 controller = new MyRpcController; 14 service = new SearchService::Stub(channel); 15 16 // Set up the request. 17 request.set_query("protocol buffers"); 18 19 // Execute the RPC. 20 service->Search(controller, request, response, protobuf::NewCallback(&Done)); 21 } 22 23 void Done() { 24 delete service; 25 delete channel; 26 delete controller; 27 }
全部的服務器類一樣實現服務接口。這提供了一種在不知道方法名字和參數的狀況下調用方法的途徑。在服務器這邊,你須要實現一個能夠註冊服務的PRC服務器。
1 using google::protobuf; 2 3 class ExampleSearchService : public SearchService { 4 public: 5 void Search(protobuf::RpcController* controller, 6 const SearchRequest* request, 7 SearchResponse* response, 8 protobuf::Closure* done) { 9 if (request->query() == "google") { 10 response->add_result()->set_url("http://www.google.com"); 11 } else if (request->query() == "protocol buffers") { 12 response->add_result()->set_url("http://protobuf.googlecode.com"); 13 } 14 done->Run(); 15 } 16 }; 17 18 int main() { 19 //你本身提供的MyRpcServer類,它不須要實現任何接口,這裏意思意思就行。 20 MyRpcServer server; 21 22 protobuf::Service* service = new ExampleSearchService; 23 server.ExportOnPort(1234, service); 24 server.Run(); 25 26 delete service; 27 return 0; 28 }
若是你不想嵌入你本身的已經存在的RPC系統,你如今可使用gRPC : 這是一種谷歌開發的語言和平臺無關的開源RPC系統。gPRC和protobuf配合的格外方便。在添加了特定的插件後,它能夠從你的.proto
文件直接生成對應的RPC代碼。不過因爲proto2和proto3之間存在兼容問題,咱們推薦你使用proto3來定義你的gPRC服務。若是你打算使用gPRC配合protobuf , 你須要3.0.0以上的版本。
每一個.proto
文件中的獨立的定義均可以被一系列的選項說明。選項不改變任何定義的總體意義,可是在特定的上下文下它們能有特定的效果。選項列表在google/protobuf/descriptor.proto
中.
有的選項是文件等級的,意味着它必須在文件最頂端寫,不能在任何消息,枚舉或者服務的定義中。也有寫選項是消息級別的,意味着它們應該寫在消息定義內,有些選項是字段級別的,意味着他們應該被寫在字段定義中。選項能夠被寫在枚舉,服務中,可是目前尚未對應的有意義的選項。
這是一些經常使用的選項:
java_package
(file option): 生成的Java的包名字。若是沒有指定這個選項那麼使用packet關鍵字的參數。不過packet關鍵字沒有辦法生成優雅的Java包名字,由於packet關鍵字不支持.
號。非Java語言忽略。option java_package = "com.example.foo";
java_outer_classname
(file option): Java最外圍的類名字和文件名。若是沒有設置,文件名就死協議文件名轉化成駝峯式的名字 : (foo_bar.proto 變成 FooBar.java
) , 非java語言忽略。option java_outer_classname = "Ponycopter";
optimize_for
(file option): 能夠是SPEED, CODE_SIZE, or LITE_RUNTIME
. 對 C++ 、Java (或者其餘三方代碼生成器)代碼生成有以下影響: option optimize_for = CODE_SIZE;
cc_generic_services
,java_generic_services
, py_generic_services
(file options): 是否生成抽象的服務代碼 分別對應C++, Java, 和Python。 因爲歷史遺留緣由,這些被默認設置爲true。// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(file option): 容許 arena allocation ,C++有效.
packed
(field option): 當你對一個repeated的整形字段設置true 的時,會使用一種更有效的編碼方式。 沒有壞處。不過在2.3.0以前的版本,若是解析器發現期待這個字段不是packed而接收的數據是packed,那麼數據會被忽略。以後的版本是安全的。若是你使用好久的版本的話請當心。
repeated int32 samples = 4 [packed=true];
deprecated
(field option): 若是被設置爲true,那麼這個字段被標記爲廢棄,新的代碼不該該使用它。在大多數語言中這個沒有實際的意義,Java會使用@Deprecated
.optional int32 old_field = 6 [deprecated=true];
Protocol Buffers 甚至容許你自定義你本身的選項。注意這是高級用法,大多數人用不到。既然選項是在google/protobuf/descriptor.proto (like FileOptions or FieldOptions)
中定義的,你只須要擴展他們定義你本身的選項。好比:
import "google/protobuf/descriptor.proto"; extend google.protobuf.MessageOptions { optional string my_option = 51234; } message MyMessage { option (my_option) = "Hello world!"; }
這裏咱們經過擴展MessageOptions
定義了一個消息級別的選項。咱們在C++中這樣讀取這個選項的值:
string value = MyMessage::descriptor()->options().GetExtension(my_option);
這裏,MyMessage::descriptor()->options()
返回了MessageOptions消息。讀取擴展選項和讀取其餘的擴展字段沒什麼區別。
Java代碼:
String value = MyProtoFile.MyMessage.getDescriptor().getOptions() .getExtension(MyProtoFile.myOption);
Python代碼:
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions() .Extensions[my_proto_file_pb2.my_option]
各類類型的選項都能被擴展。
import "google/protobuf/descriptor.proto"; extend google.protobuf.FileOptions { optional string my_file_option = 50000; } extend google.protobuf.MessageOptions { optional int32 my_message_option = 50001; } extend google.protobuf.FieldOptions { optional float my_field_option = 50002; } extend google.protobuf.EnumOptions { optional bool my_enum_option = 50003; } extend google.protobuf.EnumValueOptions { optional uint32 my_enum_value_option = 50004; } extend google.protobuf.ServiceOptions { optional MyEnum my_service_option = 50005; } extend google.protobuf.MethodOptions { optional MyMessage my_method_option = 50006; } option (my_file_option) = "Hello world!"; message MyMessage { option (my_message_option) = 1234; optional int32 foo = 1 [(my_field_option) = 4.5]; optional string bar = 2; } enum MyEnum { option (my_enum_option) = true; FOO = 1 [(my_enum_value_option) = 321]; BAR = 2; } message RequestType {} message ResponseType {} service MyService { option (my_service_option) = FOO; rpc MyMethod(RequestType) returns(ResponseType) { // Note: my_method_option has type MyMessage. We can set each field // within it using a separate "option" line. option (my_method_option).foo = 567; option (my_method_option).bar = "Some string"; } }
注意若是你在另外一個包中使用這個包定義的選項的話,你必須使用包名字做爲前綴:
// foo.proto import "google/protobuf/descriptor.proto"; package foo; extend google.protobuf.MessageOptions { optional string my_option = 51234; } // bar.proto import "foo.proto"; package bar; message MyMessage { option (foo.my_option) = "Hello world!"; }
若是你要用.proto
文件生成 C++ , Java, Python的代碼的話,你須要使用protoc來編譯.proto
文件。若是你還沒安裝這個編譯器的話,去下載一個吧。
以下執行協議的編譯:
protoc –proto_path=IMPORT_PATH –cpp_out=DST_DIR –java_out=DST_DIR –python_out=DST_DIR path/to/file.proto
.proto
文件的搜索目錄,默認是當前的工做目錄。能夠屢次使用這個參數來指定多個目錄,他們會按照順序被檢索, -I=IMPORT_PATH
是 --proto_path
的簡寫。--cpp_out
C++ code in DST_DIR.--java_out
generates Java code in DST_DIR.--python_out
generates Python code in DST_DIR.做爲一個額外的便利,若是DST_DIR
以.zip
或者.jar
來結尾的話,編譯器會自動給你打包。注意若是指定路徑已經存在的話會被覆蓋。
.proto
文件。多個文件能夠一次全給定。文件名必須是相對當前目錄的相對路徑名。每一個文件都應該在IMPORT_PATHs
指定的某個路徑下!