Protobuf 語言指南(proto3)
Protocol Buffer是Google的語言中立的,平台中立的,可擴展機制的,用於序列化結構化數據 - 對比XML,但更小,更快,更簡單。您可以定義數據的結構化,然后可以使用特殊生成的源代碼輕松地在各種數據流中使用各種語言編寫和讀取結構化數據。
定義消息類型
先來看一個非常簡單的例子。假設你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以采用如下的方式來定義消息類型的.proto文件了:
1 syntax = "proto3"; 2 3 message SearchRequest { 4 string query = 1; 5 int32 page_number = 2; 6 int32 result_per_page = 3; 7 }
-
該文件的第一行指定您正在使用
proto3
語法:如果您不這樣做,protobuf 編譯器將假定您正在使用proto2。這必須是文件的第一個非空的非注釋行。 -
所述
SearchRequest
消息定義了三個字段(名稱/值對),對應着我需要的消息內容。每個字段都有一個名稱和類型。
指定字段類型
在上面的示例中,所有字段都是標量類型:兩個整數(page_number
和result_per_page
)和一個字符串(query
)。但是,您還可以為字段指定合成類型,包括枚舉和其他消息類型。
分配標識號
正如上述文件格式,在消息定義中,每個字段都有唯一的一個數字標識符。這些標識符是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不能夠再改變。注:[1,15]之內的標識號在編碼的時候會占用一個字節。[16,2047]之內的標識號則占用2個字節。所以應該為那些頻繁出現的消息元素保留 [1,15]之內的標識號。切記:要為將來有可能添加的、頻繁出現的標識號預留一些標識號。
最小的標識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto文件中使用這些預留標識號,編譯時就會報錯。
指定字段規則
消息字段可以是以下之一:
-
單數:格式良好的消息可以包含該字段中的零個或一個(但不超過一個)。
-
repeated
:此字段可以在格式良好的消息中重復任意次數(包括零)。將保留重復值的順序。
在proto3中,repeated
數字類型的字段默認使用packed
編碼。
packed
您可以在協議緩沖區編碼中找到有關編碼的更多信息。
添加更多消息類型
可以在單個.proto
文件中定義多種消息類型。當你要定義多個相關消息時,這就很有用 了。 例如,如果要定義與SearchResponse
消息類型對應的回復消息格式,那么我在同一個.proto
文件中:
1 message SearchRequest { 2 string query = 1; 3 int32 page_number = 2; 4 int32 result_per_page = 3; 5 } 6 7 message SearchResponse { 8 ... 9 }
添加注釋
要為.proto
文件添加注釋,請使用C / C ++ - 樣式//
和/* ... */
語法。
1 / * SearchRequest表示搜索查詢,帶有分頁選項 2 *表明響應中包含哪些結果。* / 3 4 message SearchRequest { 5 string query = 1; 6 int32 page_number = 2; //我們想要哪個頁碼? 7 int32 result_per_page = 3; //每頁返回的結果數。 8 }
保留字段
當你在某次更新消息中屏蔽或者刪除了一個字段的話,未來的使用着可能在他們的更新中重用這個標簽數字來標記他們自己的字段。然后當他們加載舊的消息的時候就會出現很多問題,包括數據沖突,隱藏的bug等等。指定這個字段的標簽數字(或者名字,名字可能在序列化為JSON的時候可能沖突)標記為reserved
來保證他們不會再次被使用。如果以后的人試用的話protobuf編譯器會提示出錯。
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
注意一個reserved字段不能既有標簽數字又有名字。
.proto
文件最終生成什么
當你使用protoc
來編譯一個.proto
文件的時候,編譯器將利用你在文件中定義的類型生成你打算使用的語言的代碼文件。生成的代碼包括getting setting
接口和序列化,反序列化接口。
-
對於C ++,編譯器會從每個
.proto
文件生成一個.h
和一個.cc
文件,並為您文件中描述的每種消息類型提供一個類。 -
對於Java,編譯器生成一個
.java
文件,其中包含每種消息類型的類,以及Builder
用於創建消息類實例的特殊類。 -
Python有點不同 - Python編譯器生成一個模塊,其中包含每個消息類型的靜態描述符,然后,用一個元類在運行時創建必要的Python數據訪問類。
-
對於Go,編譯器會為
.pb.go
文件中的每種消息類型生成一個類型的文件。 -
對於Ruby,編譯器生成一個
.rb
包含消息類型的Ruby模塊的文件。 -
對於Objective-C,編譯器從每個
.proto
文件生成一個pbobjc.h
和一個pbobjc.m
文件,其中包含文件中描述的每種消息類型的類。 -
對於C#,編譯器會從每個
.proto
文件生成一個.cs
文件,其中包含文件中描述的每種消息類型的類。
您可以按照所選語言的教程(即將推出的proto3版本)了解有關為每種語言使用API的更多信息。有關更多API詳細信息,請參閱相關API參考(proto3版本即將推出)。
標量值類型
標量消息字段可以具有以下類型之一 - 該表顯示.proto
文件中指定的類型,以及自動生成的類中的相應類型:
.proto type | notes | C ++ type | Java type | Python type [2] | Type | Ruby type | C# type | PHP type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | float | double | float | |
float | float | float | float | FLOAT32 | float | float | float | |
INT32 | 使用可變長度編碼。編碼負數的效率低 - 如果您的字段可能有負值,請改用sint32。 | INT32 | INT | INT | INT32 | Fixnum or Bignum (as needed) | INT | Integer |
Int64 | 使用可變長度編碼。編碼負數的效率低 - 如果您的字段可能有負值,請改用sint64。 | Int64 | long | int / long [3] | Int64 | TWINS | long | Integer/string[5] |
UINT32 | 使用可變長度編碼。 | UINT32 | int [1] | int / long [3] | UINT32 | Fixnum or Bignum (as needed) | UINT | Integer |
UINT64 | 使用可變長度編碼。 | UINT64 | Long [1] | int / long [3] | UINT64 | TWINS | ULONG | Integer/string[5] |
SINT32 | 使用可變長度編碼。簽名的int值。這些比常規int32更有效地編碼負數。 | INT32 | INT | INT | INT32 | Fixnum or Bignum (as needed) | INT | Integer |
sint64 | 使用可變長度編碼。簽名的int值。這些比常規int64更有效地編碼負數。 | Int64 | long | int / long [3] | Int64 | TWINS | long | Integer/string[5] |
fixed32 | 總是四個字節。如果值通常大於2 28,則比uint32更有效。 | UINT32 | int [1] | int / long [3] | UINT32 | Fixnum or Bignum (as needed) | UINT | Integer |
fixed64 | 總是八個字節。如果值通常大於2 56,則比uint64更有效。 | UINT64 | Long [1] | int / long [3] | UINT64 | TWINS | ULONG | Integer/string[5] |
sfixed32 | 總是四個字節。 | INT32 | INT | INT | INT32 | Fixnum or Bignum (as needed) | INT | Integer |
sfixed64 | 總是八個字節。 | Int64 | long | int / long [3] | Int64 | TWINS | long | Integer/string[5] |
Boolean | Boolean | Boolean | Boolean | Boolean | TrueClass / FalseClass | Boolean | Boolean | |
string | 字符串必須始終包含UTF-8編碼或7位ASCII文本。 | string | string | str / unicode[4] | string | String (UTF-8) | string | string |
byte | 可以包含任意字節序列。 | string | Byte string | Strait | []byte | String (ASCII-8BIT) | Byte string | string |
在protobuf 編碼序列化消息時,您可以找到有關如何編碼這些類型的更多信息。
[1]在Java中,無符號的32位和64位整數使用它們的帶符號對應表示,最高位只是存儲在符號位中。
[2]在所有情況下,將值設置為字段將執行類型檢查以確保其有效。
[3] 64位或無符號32位整數在解碼時始終表示為long,但如果在設置字段時給出int,則可以為int。在所有情況下,該值必須適合設置時表示的類型。見[2]。
[4] Python字符串在解碼時表示為unicode,但如果給出了ASCII字符串,則可以是str(這可能會發生變化)。
[5] Integer用於64位計算機,字符串用於32位計算機。
默認值
解析消息時,如果編碼消息不包含特定的單數元素,則解析對象中的相應字段將設置為該字段的默認值。這些默認值是特定於類型的:
-
對於字符串,默認值為空字符串。
-
對於字節,默認值為空字節。
-
對於bools,默認值為false。
-
對於數字類型,默認值為零。
-
對於枚舉,默認值是第一個定義的枚舉值,該值必須為0。
-
對於消息字段,未設置該字段。它的確切值取決於語言。有關詳細信息, 請參閱生成的代碼指
重復字段的默認值為空(通常是相應語言的空列表)。
請注意,對於標量消息字段,一旦解析了消息,就無法確定字段是否顯式設置為默認值(例如,是否設置了布爾值false
)或者根本沒有設置:您應該記住這一點在定義消息類型時。例如,false
如果您不希望默認情況下也發生這種行為,那么在設置為時,沒有一個布爾值可以啟用某些行為。還要注意的是,如果一個標消息字段被設置為默認值,該值將不會在電線上連載。
有關默認值如何在生成的代碼中工作的更多詳細信息,請參閱所選語言的生成代碼指南。
枚舉
當你定義一個消息的時候,你可能希望它其中的某個字段一定是預先定義好的一組值中的一個。你如說我要在SearchRequest
中添加corpus
字段。它只能是 UNIVERSAL, WEB , IMAGES , LOCAL, NEWS ,PRODUCTS, 或者 VIDEO
。你可以很簡單的在你的消息中定義一個枚舉並且定義corpus
字段為枚舉類型,如果這個字段給出了一個不再枚舉中的值,那么解析器就會把它當作一個未知的字段。
在下面的示例中,我們添加了一個帶有所有可能值的枚舉方法Corpus
,以及一個類型的字段Corpus
:
message SearchRequest { string 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; }
如您所見,Corpus
枚舉的第一個常量映射為零:每個枚舉定義必須包含一個映射到零的常量作為其第一個元素。這是因為:
只需要將相同的值賦值給不同的枚舉項名字,你就在枚舉中你可以定義別名 。當然你得先將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
值在線上使用varint編碼,因此負值效率低,因此不建議使用。您可以enum
在消息定義中定義s,如上例所示,enum
也可以在外部定義 - 這些可以在.proto
文件的任何消息定義中重用。您還可以使用enum
語法將一個消息中聲明的類型用作另一個消息中的字段類型。 *MessageType*.*EnumType*
如果你想要使用在消息內定義的枚舉的話,使用語法 MessageType.EnumType
。 在你編譯帶有枚舉的.proto
文件的時候,如果生成的是C++或者Java代碼, 那么生成的代碼中會有對應的枚舉。
在反序列化期間,將在消息中保留無法識別的枚舉值,但是當反序列化消息時,如何表示這種值取決於語言。在支持具有超出指定符號范圍的值的開放枚舉類型的語言中,例如C ++和Go,未知的枚舉值僅作為其基礎整數表示存儲。在具有封閉枚舉類型(如Java)的語言中,枚舉中的大小寫用於表示無法識別的值,並且可以使用特殊訪問器訪問基礎整數。在任何一種情況下,如果消息被序列化,則仍然會使用消息序列化無法識別的值。
有關如何enum
在應用程序中使用消息的詳細信息,請參閱所選語言的生成代碼指南。
保留值
如果通過完全刪除枚舉條目或將其注釋掉來更新枚舉類型,則未來用戶可以在對類型進行自己的更新時重用該數值。如果以后加載相同的舊版本,這可能會導致嚴重問題.proto
,包括數據損壞,隱私錯誤等。確保不會發生這種情況的一種方法是指定已刪除條目的數值(和/或名稱,這也可能導致JSON序列化問題)reserved
。如果將來的任何用戶嘗試使用這些標識符,協議緩沖編譯器將會抱怨。您可以使用max
關鍵字指定保留的數值范圍達到最大可能值。
enum Foo { reserved 2, 15, 9 to 11, 40 to max; reserved "FOO", "BAR"; }
請注意,您不能在同一reserved
語句中混合字段名稱和數值。
使用其他消息類型
您可以使用其他消息類型作為字段類型。例如,假設你想包括Result
每個消息的SearchResponse
消息-要做到這一點,你可以定義一個Result
在同一個消息類型.proto
,然后指定類型的字段Result
中SearchResponse
:
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
導入定義
在上面的示例中,Result
消息類型在同一文件中定義SearchResponse
,如果要用作字段類型的消息類型已在另一個.proto
文件中定義,該怎么辦?
您可以.proto
通過導入來使用其他文件中的定義。要導入其他.proto
的定義,請在文件頂部添加import語句:
import“myproject / other_protos.proto”;
默認情況下,您只能使用直接導入.proto
文件中的定義。但是,有時您可能需要將.proto
文件移動到新位置。.proto
現在,您可以.proto
在舊位置放置一個虛擬文件,以使用該import public
概念將所有導入轉發到新位置,而不是直接移動文件並在一次更改中更新所有調用站點。import public
任何導入包含該import public
語句的proto的人都可以傳遞依賴關系。例如:
// new.proto // All definitions are moved here
// old.proto //This is the proto that all clients are importing. import public“new.proto”; import“other.proto”;
// client.proto import "old.proto"; //您使用old.proto和new.proto中的定義,但不使用other.proto
協議編譯器使用-I
/ --proto_path
flag 在協議編譯器命令行中指定的一組目錄中搜索導入的文件 。如果沒有給出標志,它將查找調用編譯器的目錄。通常,您應該將--proto_path
標志設置為項目的根目錄,並對所有導入使用完全限定名稱。
使用proto2消息類型
可以導入proto2消息類型並在proto3消息中使用它們,反之亦然。但是,proto2枚舉不能直接用於proto3語法(如果導入的proto2消息使用它們就可以了)。
嵌套類型
您可以在其他消息類型中定義和使用消息類型,如下例所示 - 此處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; }
您可以根據需要深入嵌套消息:
message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 int64 ival = 1; bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 int32 ival = 1; bool booly = 2; } } }
更新消息類型
如果現有的消息類型不再滿足您的所有需求 - 例如,您希望消息格式具有額外的字段 - 但您仍然希望使用使用舊格式創建的代碼,請不要擔心!在不破壞任何現有代碼的情況下更新消息類型非常簡單。請記住以下規則:
-
請勿更改任何現有字段的字段編號。
-
如果添加新字段,則使用“舊”消息格式按代碼序列化的任何消息仍可由新生成的代碼進行解析。您應該記住這些元素的默認值,以便新代碼可以正確地與舊代碼生成的消息進行交互。同樣,您的新代碼創建的消息可以由舊代碼解析:舊的二進制文件在解析時只是忽略新字段。有關詳細信息,請參閱“ 未知字段”部分
-
只要在更新的消息類型中不再使用字段編號,就可以刪除字段。您可能希望重命名該字段,可能添加前綴“OBSOLETE_”,或者保留字段編號,以便您的未來用戶
.proto
不會意外地重復使用該編號。 -
int32
,uint32
,int64
,uint64
,和bool
都是兼容的-這意味着你可以改變這些類型到另一個的一個場不破壞forwards-或向后兼容。如果從導線中解析出一個不符合相應類型的數字,您將獲得與在C ++中將該數字轉換為該類型相同的效果(例如,如果將64位數字作為int32讀取,它將被截斷為32位)。 -
sint32
並且sint64
彼此兼容但與其他整數類型不兼容。 -
string``bytes
只要字節是有效的UTF-8 ,它們是兼容的。 -
bytes
如果字節包含消息的編碼版本,則嵌入消息是兼容的。 -
fixed32
與兼容sfixed32
,並fixed64
用sfixed64
。 -
enum
與兼容int32
,uint32
,int64
,和uint64
電線格式條款(注意,如果他們不適合的值將被截斷)。但請注意,在反序列化消息時,客戶端代碼可能會以不同方式對待它們:例如,enum
將在消息中保留未識別的proto3 類型,但在反序列化消息時如何表示這種類型取決於語言。Int字段總是保留它們的價值。 -
將單個值更改為新 成員
oneof
是安全且二進制兼容的。oneof
如果您確定沒有代碼一次設置多個字段,則將多個字段移動到新字段可能是安全的。將任何字段移動到現有字段oneof
並不安全。
未知字段
未知字段是格式良好的協議緩沖區序列化數據,表示解析器無法識別的字段。例如,當舊二進制文件解析具有新字段的新二進制文件發送的數據時,這些新字段將成為舊二進制文件中的未知字段。
最初,proto3消息在解析期間總是丟棄未知字段,但在3.5版本中,我們重新引入了保存未知字段以匹配proto2行為。在版本3.5及更高版本中,未知字段在解析期間保留並包含在序列化輸出中。
任何
該Any
消息類型,可以使用郵件作為嵌入式類型,而不必自己.proto定義。一個Any
含有任意的序列化消息bytes
,以充當一個全局唯一標識符和解析到該消息的類型的URL一起。要使用該Any
類型,您需要導入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*
不同的語言實現將支持運行時庫佣工類型安全的方式打包和解包的任何值-例如,在Java中,任何類型都會有特殊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類型的運行時庫。
Oneof
如果您有一個包含許多字段的消息,並且最多只能同時設置一個字段,則可以使用oneof功能強制執行此行為並節省內存。
除了一個共享內存中的所有字段之外,其中一個字段類似於常規字段,並且最多可以同時設置一個字段。設置oneof的任何成員會自動清除所有其他成員。您可以使用特殊case()
或WhichOneof()
方法檢查oneof中的哪個值(如果有),具體取決於您選擇的語言。
使用Oneof
要在您中定義oneof,請.proto
使用oneof
關鍵字后跟您的oneof名稱,在這種情況下test_oneof
:
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }
然后,將oneof字段添加到oneof定義中。您可以添加任何類型的字段,但不能使用repeated
字段。
在生成的代碼中,oneof字段與常規字段具有相同的getter和setter。您還可以使用特殊方法檢查oneof中的值(如果有)。您可以在相關API參考中找到有關所選語言的oneof API的更多信息。
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的多個成員,則在解析的消息中僅使用看到的最后一個成員。
-
一個不可能
repeated
。 -
Reflection API適用於其中一個字段。
-
如果您使用的是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()
兩個消息與oneofs,每個消息最終將與另一個消息結果:在下面的例子中,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返回的值None
/ NOT_SET
,這可能意味着oneof尚未設置或已在不同版本的oneof的被設置為一個字段。沒有辦法區分,因為沒有辦法知道線上的未知字段是否是其中一個成員。
標簽重用問題
-
將字段移入或移出oneof:在序列化和解析消息后,您可能會丟失一些信息(某些字段將被清除)。但是,您可以安全地將單個字段移動到新的 oneof中,並且如果已知只有一個字段被設置,則可以移動多個字段。
-
刪除oneof字段並將其添加回:在序列化和解析消息后,這可能會清除當前設置的oneof字段。
-
拆分或合並oneof:這與移動常規字段有類似的問題。
Maps
如果要在數據定義中創建關聯映射,protobuf提供了一種方便的快捷方式語法:
map < key_type ,value_type > map_field = N ;
這里key_type可以是任意整形或者字符串。而value_tpye 可以是任意類型。 舉個例子,如果你打算創建一個Project表,每個Project關聯到一個字符串上的話 :
map < string ,Project > projects = 3 ;
-
map字段不能
repeated
。 -
map值的有線格式排序和地圖迭代排序未定義,因此您不能依賴於特定順序的map項目。
-
.proto
生成文本格式時,地圖按鍵排序。數字鍵按數字排序。 -
從線路解析或合並時,如果有重復的映射鍵,則使用最后看到的鍵。從文本格式解析映射時,如果存在重復鍵,則解析可能會失敗。
-
如果為映射字段提供鍵但沒有值,則字段序列化時的行為取決於語言。在C ++,Java和Python中,類型的默認值是序列化的,而在其他語言中沒有任何序列化。
生成的地圖API目前可用於所有proto3支持的語言。您可以在相關API參考中找到有關所選語言的地圖API的更多信息。
向后兼容性
在通訊中,map等價與下面的定義, 這樣不支持Map的版本也可以解析你的消息:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
任何支持映射的協議緩沖區實現都必須生成和接受上述定義可以接受的數據。
包
您可以向.proto
文件添加package
可選說明符,以防止協議消息類型之間的名稱沖突。
package foo.bar; message Open { ... }
然后,您可以在定義消息類型的字段時使用包說明符:
message Foo { ... foo.bar.Open open = 1; ... }
包名字的實現取決於你工作的具體編程語言:
-
在C ++中,生成的類包含在C ++命名空間中。例如,
Open
將在命名空間中foo::bar
。 -
在Java中,該包用作Java包,除非您
option java_package
在.proto
文件中明確提供了該包。 -
在Python中,package指令被忽略,因為Python模塊是根據它們在文件系統中的位置進行組織的。
-
在Go中,該包用作Go包名稱,除非您
option go_package
在.proto
文件中明確提供。 -
在Ruby中,生成的類包含在嵌套的Ruby命名空間內,轉換為所需的Ruby大寫形式(首字母大寫;如果第一個字符不是字母,
PB_
則前置)。例如,Open
將在命名空間中Foo::Bar
。 -
在C#中,包轉換為PascalCase后用作命名空間,除非您
option csharp_namespace
在.proto
文件中明確提供。例如,Open
將在命名空間中Foo.Bar
。
包和名稱解析
協議緩沖區語言中的類型名稱解析與C ++類似:首先搜索最里面的范圍,然后搜索下一個范圍,依此類推,每個包被認為是其父包的“內部”。一個領先的'。' (例如,.foo.bar.Baz
)意味着從最外層的范圍開始。
protobuf 編譯器通過解析導入的.proto
文件來解析所有類型名稱。每種語言的代碼生成器都知道如何使用該語言引用每種類型,即使它具有不同的范圍規則。
定義服務
如果要將消息類型與RPC(遠程過程調用)系統一起使用,則可以在.proto
文件中定義RPC服務接口,protobuf 編譯器將使用您選擇的語言生成服務接口代碼和存根。因此,例如,如果要定義RPC服務請求方法為:SearchRequest
和返回方法為:SearchResponse
,可以.proto
按如下方式在文件中定義它:
service SearchService {
rpc Search(SearchRequest)returns(SearchResponse);
}
與協議緩沖區一起使用的最簡單的RPC系統是gRPC:一種由Google開發的,平台中立的開源RPC系統。gRPC特別適用於protobuf,並允許在您的.proto
文件中使用特殊的protobuf 編譯器插件直接生成相關的RPC代碼。
如果您不想使用gRPC,也可以將protobuf與您自己的RPC實現一起使用。您可以在Proto2語言指南中找到更多相關信息。
還有一些正在進行的第三方項目使用Protocol Buffers開發RPC實現。有關我們了解的項目的鏈接列表,請參閱第三方加載項wiki頁面。
JSON映射
Proto3支持JSON中的規范編碼,使得在系統之間共享數據變得更加容易。在下表中逐個類型地描述編碼。
如果JSON編碼數據中缺少值null
,或者其值為,則在解析為協議緩沖區時,它將被解釋為適當的默認值。如果字段在協議緩沖區中具有默認值,則默認情況下將在JSON編碼數據中省略該字段以節省空間。實現可以提供用於在JSON編碼的輸出中發出具有默認值的字段的選項。
proto3 | JSON | JSON示例 | 筆記 |
---|---|---|---|
message | object | {"fooBar": v, "g": null,…} |
生成JSON對象。消息字段名稱映射到小寫駝峰並成為JSON對象鍵。如果json_name 指定了field選項,則指定的值將用作鍵。解析器接受小寫駝峰名稱(或json_name 選項指定的名稱)和原始proto字段名稱。null 是所有字段類型的可接受值,並將其視為相應字段類型的默認值。 |
eunm | String | "FOO_BAR" |
使用proto中指定的枚舉值的名稱。解析器接受枚舉名稱和整數值。 |
map<K,V> | object | {"k": v, …} |
所有鍵都轉換為字符串。 |
repeated V. | array | [v, …] |
null 被接受為空列表[]。 |
bool | true,false | true, false |
|
string | string | "Hello World!" |
|
bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" |
JSON值將是使用帶填充的標准base64編碼編碼為字符串的數據。接受帶有/不帶填充的標准或URL安全base64編碼。 |
int32,fixed32,uint32 | string | 1, -10, 0 |
JSON值將是十進制數。接受數字或字符串。 |
int64,fixed64,uint64 | string | "1", "-10" |
JSON值將是十進制字符串。接受數字或字符串。 |
float,double | number | 1.1, -10.0, 0, "NaN","Infinity" |
JSON值將是一個數字或一個特殊字符串值“NaN”,“Infinity”和“-Infinity”。接受數字或字符串。指數表示法也被接受。 |
any | object | {"@type": "url", "f": v, … } |
如果Any包含具有特殊JSON映射的值,則將按如下方式進行轉換:。否則,該值將轉換為JSON對象,並將插入該字段以指示實際的數據類型。{"@type": xxx, "value": yyy}``"@type" |
Timestamp | string | "1972-01-01T10:00:20.021Z" |
使用RFC 3339,其中生成的輸出將始終被Z標准化並使用0,3,6或9個小數位。也接受“Z”以外的偏移。 |
Duration | string | "1.000340012s", "1s" |
生成的輸出始終包含0,3,6或9個小數位,具體取決於所需的精度,后跟后綴“s”。接受的是任何小數位(也沒有),只要它們符合納秒精度並且后綴“s”是必需的。 |
Struct | object |
{ … } |
任何JSON對象。見。struct.proto |
Wrapper types | various types | 2, "2", "foo", true,"true", null, 0, … |
包裝器在JSON中使用與包裝基元類型相同的表示形式,除了null 在數據轉換和傳輸期間允許和保留的表示形式。 |
FieldMask | string | "f.fooBar,h" |
見。field_mask.proto |
ListValue | array | [foo, bar, …] |
|
Value | value | 任何JSON值 | |
NullValue | null | JSON null |
JSON選項
proto3 JSON實現可以提供以下選項:
-
使用默認值發出字段:默認情況下,proto3 JSON輸出中省略了具有默認值的字段。實現可以提供覆蓋此行為的選項,並使用其默認值輸出字段。
-
忽略未知字段:默認情況下,Proto3 JSON解析器應拒絕未知字段,但可以提供忽略解析中未知字段的選項。
-
使用proto字段名稱而不是小寫駝峰名稱:默認情況下,proto3 JSON打印機應將字段名稱轉換為小寫駝峰並將其用作JSON名稱。實現可以提供使用proto字段名稱作為JSON名稱的選項。Proto3 JSON解析器需要接受轉換后的小寫駝峰名稱和proto字段名稱。
-
將枚舉值發送為整數而不是字符串:默認情況下,在JSON輸出中使用枚舉值的名稱。可以提供選項以使用枚舉值的數值。
選項
.proto
文件中的各個聲明可以使用許多選項進行注釋。選項不會更改聲明的整體含義,但可能會影響在特定上下文中處理它的方式。可用選項的完整列表在中定義google/protobuf/descriptor.proto
。
一些選項是文件級選項,這意味着它們應該在頂級范圍內編寫,而不是在任何消息,枚舉或服務定義中。一些選項是消息級選項,這意味着它們應該寫在消息定義中。一些選項是字段級選項,這意味着它們應該寫在字段定義中。選項也可以寫在枚舉類型,枚舉值,服務類型和服務方法上; 但是,目前沒有任何有用的選擇。
以下是一些最常用的選項:
-
java_package
(文件選項):用於生成的Java類的包。如果.proto
文件中沒有給出顯式選項java_package
,則默認情況下將使用proto包(使用文件中的“package”關鍵字指定 .proto )。但是,proto包通常不能生成好的Java包,因為proto包不會以反向域名開頭。如果不生成Java代碼,則此選項無效。option java_package =“com.example.foo”;
-
java_multiple_files
(文件選項):導致在包級別定義頂級消息,枚舉和服務,而不是在.proto文件之后命名的外部類中。
option java_multiple_files = true;
-
java_outer_classname
(file option):要生成的最外層Java類(以及文件名)的類名。如果.proto
文件中沒有指定java_outer_classname
,則通過將.proto
文件名轉換為駝峰格式(因此foo_bar.proto
成為FooBar.java
)來構造類名。如果不生成Java代碼,則此選項無效。
option java_outer_classname =“Ponycopter”;
-
optimize_for
(文件選項):可以設置為
SPEED
,CODE_SIZE
或LITE_RUNTIME
。這會以下列方式影響C ++和Java代碼生成器(可能還有第三方生成器):-
SPEED
(默認值):protobuf 編譯器將生成用於對消息類型進行序列化,解析和執行其他常見操作的代碼。此代碼經過高度優化。 -
CODE_SIZE
:protobuf 編譯器將生成最少的類,並依賴於基於反射的共享代碼來實現序列化,解析和各種其他操作。因此生成的代碼比使用SPEED
小得多,但操作會更慢。類仍將實現與SPEED
模式完全相同的公共API 。此模式在包含非常大數量的.proto
文件的應用程序中最有用,並且不需要所有文件都非常快速。 -
LITE_RUNTIME
:protobuf 編譯器將生成僅依賴於“lite”運行時庫(libprotobuf-lite
而不是libprotobuf
)的類。精簡版運行時比整個庫小得多(大約小一個數量級),但省略了描述符和反射等特定功能。這對於在移動電話等受限平台上運行的應用程序尤其有用。編譯器仍然會像在SPEED
模式中一樣生成所有方法的快速實現。生成的類將僅實現MessageLite
每種語言的接口,該接口僅提供完整Message
接口的方法的子集。
option optimize_for = CODE_SIZE;
-
-
cc_enable_arenas
(文件選項):為C ++生成的代碼啟用競技場分配。 -
objc_class_prefix
(文件選項):設置Objective-C類前綴,該前綴預先添加到此.proto的所有Objective-C生成的類和枚舉中。沒有默認值。您應該使用Apple建議的 3-5個大寫字符之間的前綴。請注意,Apple保留所有2個字母的前綴。 -
deprecated
(字段選項):如果設置為true
,則表示該字段已棄用,新代碼不應使用該字段。在大多數語言中,這沒有實際效果。在Java中,這成為一個@Deprecated
注釋。將來,其他特定於語言的代碼生成器可能會在字段的訪問器上生成棄用注釋,這將導致在編譯嘗試使用該字段的代碼時發出警告。如果任何人都沒有使用該字段,並且您希望阻止新用戶使用該字段,請考慮使用保留語句替換字段聲明。int32 old_field = 6 [deprecated = true];
自定義選項
Protocol Buffers還允許您定義和使用自己的選項。這是大多數人不需要的高級功能。如果您確實認為需要創建自己的選項,請參閱Proto2語言指南以獲取詳細信息。請注意,創建自定義選項使用的擴展名僅允許用於proto3中的自定義選項。
生成您的類
根據實際工作需要,生成以下對應語言的自定義消息類型Java,Python,C ++,Go, Ruby, Objective-C,或C#的.proto
文件,你需要運行protobuf 編譯器protoc
上.proto
。如果尚未安裝編譯器,請下載該軟件包並按照自述文件中的說明進行操作。對於Go,您還需要為編譯器安裝一個特殊的代碼生成器插件:您可以在GitHub上的golang / protobuf存儲庫中找到這個和安裝說明。
Protobuf 編譯器的調用如下:
protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR path / to / file .proto
-
IMPORT_PATH
指定.proto
解析import
指令時在其中查找文件的目錄。如果省略,則使用當前目錄。可以通過--proto_path
多次傳遞選項來指定多個導入目錄; 他們將按順序搜索。 可以用作簡短的形式。-I=*IMPORT_PATH*``--proto_path
-
您可以提供一個或多個輸出指令:
-
--cpp_out
生成C ++代碼DST_DIR
。有關更多信息,請參閱C ++生成的代碼參考。 -
--java_out
生成Java代碼DST_DIR
。請參閱的Java生成的代碼參考更多。 -
--python_out
生成Python代碼DST_DIR
。看到的Python生成的代碼的參考更多。 -
--go_out
生成Go代碼DST_DIR
。有關更多信息,請參閱Go生成代碼參考。 -
--ruby_out
生成Ruby代碼DST_DIR
。Ruby生成的代碼參考即將推出! -
--objc_out
生成Objective-C代碼DST_DIR
。有關更多信息,請參閱Objective-C生成的代碼參考。 -
--csharp_out
生成C#代碼DST_DIR
。有關更多信息,請參閱C#生成的代碼參考。 -
--php_out
生成PHP代碼DST_DIR
。看到PHP生成的代碼的參考更多。
為了方便起見,如果DST_DIR結束於
.zip
或.jar
,編譯器會將輸出寫入具有給定名稱的單個ZIP格式存檔文件。.jar
輸出還將根據Java JAR規范的要求提供清單文件。請注意,如果輸出存檔已存在,則會被覆蓋; 編譯器不夠智能,無法將文件添加到現有存檔中。 -
-
您必須提供一個或多個
.proto
文件作為輸入。.proto
可以一次指定多個文件。雖然文件是相對於當前目錄命名的,但每個文件必須位於其中一個文件中,IMPORT_PATH
以便編譯器可以確定其規范名稱。
其它
[Protocol Buffers 官網] https://developers.google.com/protocol-buffers/docs/proto3 Language Guide(proto3)
關於Protobuf 語言指南(proto3)詳解到這里就結束了。
對了,也有使用protobuf整合的Netty項目工程地址: https://github.com/sanshengshui/netty-learning-example/tree/master/netty-springboot-protobuf
原創不易,如果感覺不錯,希望給個推薦!您的支持是我寫作的最大動力!
版權聲明: 作者:穆書偉 博客園出處:https://www.cnblogs.com/sanshengshui github出處:https://github.com/sanshengshui 個人博客出處:https://sanshengshui.github.io/