英文原文:
Language Guide (proto3)
中文出處:
Protobuf語言指南
[譯]Protobuf 語法指南
中文出處是proto2的譯文,proto3的英文出現(xiàn)后在原來基礎(chǔ)上增改了,水平有限,還請指正
這個指南描述了如何使用Protocol buffer 語言去描述你的protocol buffer 數(shù)據(jù), 包括 .proto文件符號和如何從.proto文件生成類。包含了proto2版本的protocol buffer語言:對于老版本的proto3 符號,請見Proto2 Language Guide(以及中文譯本,抄了很多這里的感謝下老版本的翻譯者)
本文是一個參考指南——如果要查看如何使用本文中描述的多個特性的循序漸進(jìn)的例子,請在教程中查找需要的語言的教程。
定義一個消息類型
先來看一個非常簡單的例子。假設(shè)你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結(jié)果所在的頁數(shù),以及每一頁多少條查詢結(jié)果??梢圆捎萌缦碌姆绞絹矶x消息類型的.proto文件了:
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 文件的第一行指定了你正在使用proto3語法:如果你沒有指定這個,編譯器會使用proto2。這個指定語法行必須是文件的非空非注釋的第一個行。
- SearchRequest消息格式有3個字段,在消息中承載的數(shù)據(jù)分別對應(yīng)于每一個字段。其中每個字段都有一個名字和一種類型。
指定字段類型
在上面的例子中,所有字段都是標(biāo)量類型:兩個整型(page_number和result_per_page),一個string類型(query)。當(dāng)然,你也可以為字段指定其他的合成類型,包括枚舉(enumerations)或其他消息類型。
分配標(biāo)識號
正如你所見,在消息定義中,每個字段都有唯一的一個數(shù)字標(biāo)識符。這些標(biāo)識符是用來在消息的二進(jìn)制格式中識別各個字段的,一旦開始使用就不能夠再改變。注:[1,15]之內(nèi)的標(biāo)識號在編碼的時候會占用一個字節(jié)。[16,2047]之內(nèi)的標(biāo)識號則占用2個字節(jié)。所以應(yīng)該為那些頻繁出現(xiàn)的消息元素保留 [1,15]之內(nèi)的標(biāo)識號。切記:要為將來有可能添加的、頻繁出現(xiàn)的標(biāo)識號預(yù)留一些標(biāo)識號。
最小的標(biāo)識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (從FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的標(biāo)識號, Protobuf協(xié)議實現(xiàn)中對這些進(jìn)行了預(yù)留。如果非要在.proto文件中使用這些預(yù)留標(biāo)識號,編譯時就會報警。同樣你也不能使用早期保留的標(biāo)識號。
指定字段規(guī)則
所指定的消息字段修飾符必須是如下之一:
singular:一個格式良好的消息應(yīng)該有0個或者1個這種字段(但是不能超過1個)。
-
repeated:在一個格式良好的消息中,這種字段可以重復(fù)任意多次(包括0次)。重復(fù)的值的順序會被保留。
在proto3中,repeated的標(biāo)量域默認(rèn)情況蝦使用packed。
你可以了解更多的pakced屬性在Protocol Buffer 編碼
添加更多消息類型
在一個.proto文件中可以定義多個消息類型。在定義多個相關(guān)的消息的時候,這一點特別有用——例如,如果想定義與SearchResponse消息類型對應(yīng)的回復(fù)消息格式的話,你可以將它添加到相同的.proto文件中,如:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加注釋
向.proto文件添加注釋,可以使用C/C++/java風(fēng)格的雙斜杠(//) 語法格式,如:
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.
}
保留標(biāo)識符(Reserved)
如果你通過刪除或者注釋所有域,以后的用戶可以重用標(biāo)識號當(dāng)你重新更新類型的時候。如果你使用舊版本加載相同的.proto文件這會導(dǎo)致嚴(yán)重的問題,包括數(shù)據(jù)損壞、隱私錯誤等等?,F(xiàn)在有一種確保不會發(fā)生這種情況的方法就是指定保留標(biāo)識符(and/or names, which can also cause issues for JSON serialization不明白什么意思),protocol buffer的編譯器會警告未來嘗試使用這些域標(biāo)識符的用戶。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注:不要在同一行reserved聲明中同時聲明域名字和標(biāo)識號
從.proto文件生成了什么?
當(dāng)用protocol buffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼可以操作在.proto文件中定義的消息類型,包括獲取、設(shè)置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。
- 對C++來說,編譯器會為每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應(yīng)的類。
- 對Java來說,編譯器為每一個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來創(chuàng)建消息類接口的)。
- 對Python來說,有點不太一樣——Python編譯器為.proto文件中的每個消息類型生成一個含有靜態(tài)描述符的模塊,,該模塊與一個元類(metaclass)在運行時(runtime)被用來創(chuàng)建所需的Python數(shù)據(jù)訪問類。
- 對go來說,編譯器會位每個消息類型生成了一個.pd.go文件。
- 對于Ruby來說,編譯器會為每個消息類型生成了一個.rb文件。
- javaNano來說,編譯器輸出類似域java但是沒有Builder類
- 對于Objective-C來說,編譯器會為每個消息類型生成了一個pbobjc.h文件和pbobjcm文件,.proto文件中的每一個消息有一個對應(yīng)的類。
- 對于C#來說,編譯器會為每個消息類型生成了一個.cs文件,.proto文件中的每一個消息有一個對應(yīng)的類。
你可以從如下的文檔鏈接中獲取每種語言更多API(proto3版本的內(nèi)容很快就公布)。API Reference
標(biāo)量數(shù)值類型
一個標(biāo)量消息字段可以含有一個如下的類型——該表格展示了定義于.proto文件中的類型,以及與之對應(yīng)的、在自動生成的訪問類中定義的類型:
| .proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | Float | double | float | |
| float | float | float | float | float32 | Float | float | float | |
| int32 | 使用變長編碼,對于負(fù)值的效率很低,如果你的域有可能有負(fù)值,請使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據(jù)需要) | int | integer |
| uint32 | 使用變長編碼 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根據(jù)需要) | uint | integer |
| uint64 | 使用變長編碼 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
| sint32 | 使用變長編碼,這些編碼在負(fù)值時比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據(jù)需要) | int | integer |
| sint64 | 使用變長編碼,有符號的整型值。編碼時比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long | integer/string |
| fixed32 | 總是4個字節(jié),如果數(shù)值總是比總是比228大的話,這個類型會比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根據(jù)需要) | uint | integer |
| fixed64 | 總是8個字節(jié),如果數(shù)值總是比總是比256大的話,這個類型會比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string |
| sfixed32 | 總是4個字節(jié) | int32 | int | int | int32 | Fixnum 或者 Bignum(根據(jù)需要) | int | integer |
| sfixed64 | 總是8個字節(jié) | int64 | long | int/long | int64 | Bignum | long | integer/string |
| bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | |
| string | 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 | string | String | str/unicode | string | String (UTF-8) | string | string |
| bytes | 可能包含任意順序的字節(jié)數(shù)據(jù)。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string |
你可以在文章Protocol Buffer 編碼中,找到更多“序列化消息時各種類型如何編碼”的信息。
- 在java中,無符號32位和64位整型被表示成他們的整型對應(yīng)形似,最高位被儲存在標(biāo)志位中。
- 對于所有的情況,設(shè)定值會執(zhí)行類型檢查以確保此值是有效。
- 64位或者無符號32位整型在解碼時被表示成為ilong,但是在設(shè)置時可以使用int型值設(shè)定,在所有的情況下,值必須符合其設(shè)置其類型的要求。
- python中string被表示成在解碼時表示成unicode。但是一個ASCIIstring可以被表示成str類型。
- Integer在64位的機器上使用,string在32位機器上使用
默認(rèn)值
當(dāng)一個消息被解析的時候,如果被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應(yīng)的域被設(shè)置位一個默認(rèn)值,對于不同類型指定如下:
對于strings,默認(rèn)是一個空string
對于bytes,默認(rèn)是一個空的bytes
對于bools,默認(rèn)是false
對于數(shù)值類型,默認(rèn)是0
對于枚舉,默認(rèn)是第一個定義的枚舉值,必須為0;
-
對于消息類型(message),域沒有被設(shè)置,確切的消息是根據(jù)語言確定的,詳見generated code guide
對于可重復(fù)域的默認(rèn)值是空(通常情況下是對應(yīng)語言中空列表)。
注:對于標(biāo)量消息域,一旦消息被解析,就無法判斷域釋放被設(shè)置為默認(rèn)值(例如,例如boolean值是否被設(shè)置為false)還是根本沒有被設(shè)置。你應(yīng)該在定義你的消息類型時非常注意。例如,比如你不應(yīng)該定義boolean的默認(rèn)值false作為任何行為的觸發(fā)方式。也應(yīng)該注意如果一個標(biāo)量消息域被設(shè)置為標(biāo)志位,這個值不應(yīng)該被序列化傳輸。
查看generated code guide選擇你的語言的默認(rèn)值的工作細(xì)節(jié)。
枚舉
當(dāng)需要定義一個消息類型的時候,可能想為一個字段指定某“預(yù)定義值序列”中的一個值。例如,假設(shè)要為每一個SearchRequest消息添加一個 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現(xiàn)這一點:通過向消息定義中添加一個枚舉(enum)并且為每個可能的值定義一個常量就可以了。
在下面的例子中,在消息格式中添加了一個叫做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枚舉的第一個常量映射為0:每個枚舉類型必須將其第一個類型映射為0,這是因為:
必須有有一個0值,我們可以用這個0值作為默認(rèn)值。
-
這個零值必須為第一個元素,為了兼容proto2語義,枚舉類的第一個值總是默認(rèn)值。
你可以通過將不同的枚舉常量指定位相同的值。如果這樣做你需要將allow_alias設(shè)定位true,否則編譯器會在別名的地方產(chǎn)生一個錯誤信息。
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位整型值的范圍內(nèi)。因為enum值是使用可變編碼方式的,對負(fù)數(shù)不夠高效,因此不推薦在enum中使用負(fù)數(shù)。如上例所示,可以在 一個消息定義的內(nèi)部或外部定義枚舉——這些枚舉可以在.proto文件中的任何消息定義里重用。當(dāng)然也可以在一個消息中聲明一個枚舉類型,而在另一個不同 的消息中使用它——采用MessageType.EnumType的語法格式。
當(dāng)對一個使用了枚舉的.proto文件運行protocol buffer編譯器的時候,生成的代碼中將有一個對應(yīng)的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在運行時生成的類中創(chuàng)建一系列的整型值符號常量(symbolic constants)。
在反序列化的過程中,無法識別的枚舉值會被保存在消息中,雖然這種表示方式需要依據(jù)所使用語言而定。在那些支持開放枚舉類型超出指定范圍之外的語言中(例如C++和Go),為識別的值會被表示成所支持的整型。在使用封閉枚舉類型的語言中(Java),使用枚舉中的一個類型來表示未識別的值,并且可以使用所支持整型來訪問。在其他情況下,如果解析的消息被序列號,未識別的值將保持原樣。
關(guān)于如何在你的應(yīng)用程序的消息中使用枚舉的更多信息,請查看所選擇的語言generated code guide
使用其他消息類型
你可以將其他消息類型用作字段類型。例如,假設(shè)在每一個SearchResponse消息中包含Result消息,此時可以在相同的.proto文件中定義一個Result消息類型,然后在SearchResponse消息中指定一個Result類型的字段,如:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
導(dǎo)入定義
在上面的例子中,Result消息類型與SearchResponse是定義在同一文件中的。如果想要使用的消息類型已經(jīng)在其他.proto文件中已經(jīng)定義過了呢?
你可以通過導(dǎo)入(importing)其他.proto文件中的定義來使用它們。要導(dǎo)入其他.proto文件的定義,你需要在你的文件中添加一個導(dǎo)入聲明,如:
import "myproject/other_protos.proto";
默認(rèn)情況下你只能使用直接導(dǎo)入的.proto文件中的定義. 然而, 有時候你需要移動一個.proto文件到一個新的位置, 可以不直接移動.proto文件, 只需放入一個偽 .proto 文件在老的位置, 然后使用import public轉(zhuǎn)向新的位置。import public 依賴性會通過任意導(dǎo)入包含import public聲明的proto文件傳遞。例如:
// 這是新的proto
// All definitions are moved here
// 這是久的proto
// 這是所有客戶端正在導(dǎo)入的包
import public "new.proto";
import "other.proto";
// 客戶端proto
import "old.proto";
// 現(xiàn)在你可以使用新久兩種包的proto定義了。
通過在編譯器命令行參數(shù)中使用-I/--proto_pathprotocal 編譯器會在指定目錄搜索要導(dǎo)入的文件。如果沒有給出標(biāo)志,編譯器會搜索編譯命令被調(diào)用的目錄。通常你只要指定proto_path標(biāo)志為你的工程根目錄就好。并且指定好導(dǎo)入的正確名稱就好。
使用proto2消息類型
在你的proto3消息中導(dǎo)入proto2的消息類型也是可以的,反之亦然,然后proto2枚舉不可以直接在proto3的標(biāo)識符中使用(如果僅僅在proto2消息中使用是可以的)。
嵌套類型
你可以在其他消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內(nèi),如:
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;
}
當(dāng)然,你也可以將消息嵌套任意多層,如:
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;
}
}
}
更新一個消息類型
如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。不用擔(dān)心!更新消息而不破壞已有代碼是非常簡單的。在更新時只要記住以下的規(guī)則即可。
- 不要更改任何已有的字段的數(shù)值標(biāo)識。
- 如果你增加新的字段,使用舊格式的字段仍然可以被你新產(chǎn)生的代碼所解析。你應(yīng)該記住這些元素的默認(rèn)值這樣你的新代碼就可以以適當(dāng)?shù)姆绞胶团f代碼產(chǎn)生的數(shù)據(jù)交互。相似的,通過新代碼產(chǎn)生的消息也可以被舊代碼解析:只不過新的字段會被忽視掉。注意,未被識別的字段會在反序列化的過程中丟棄掉,所以如果消息再被傳遞給新的代碼,新的字段依然是不可用的(這和proto2中的行為是不同的,在proto2中未定義的域依然會隨著消息被序列化)
- 非required的字段可以移除——只要它們的標(biāo)識號在新的消息類型中不再使用(更好的做法可能是重命名那個字段,例如在字段前添加“OBSOLETE_”前綴,那樣的話,使用的.proto文件的用戶將來就不會無意中重新使用了那些不該使用的標(biāo)識號)。
- int32, uint32, int64, uint64,和bool是全部兼容的,這意味著可以將這些類型中的一個轉(zhuǎn)換為另外一個,而不會破壞向前、 向后的兼容性。如果解析出來的數(shù)字與對應(yīng)的類型不相符,那么結(jié)果就像在C++中對它進(jìn)行了強制類型轉(zhuǎn)換一樣(例如,如果把一個64位數(shù)字當(dāng)作int32來 讀取,那么它就會被截斷為32位的數(shù)字)。
- sint32和sint64是互相兼容的,但是它們與其他整數(shù)類型不兼容。
- string和bytes是兼容的——只要bytes是有效的UTF-8編碼。
- 嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。
- fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。
- 枚舉類型與int32,uint32,int64和uint64相兼容(注意如果值不相兼容則會被截斷),然而在客戶端反序列化之后他們可能會有不同的處理方式,例如,未識別的proto3枚舉類型會被保留在消息中,但是他的表示方式會依照語言而定。int類型的字段總會保留他們的
Any
Any類型消息允許你在沒有指定他們的.proto定義的情況下使用消息作為一個嵌套類型。一個Any類型包括一個可以被序列化bytes類型的任意消息,以及一個URL作為一個全局標(biāo)識符和解析消息類型。為了使用Any類型,你需要導(dǎo)入import google/protobuf/any.proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
對于給定的消息類型的默認(rèn)類型URL是type.googleapis.com/packagename.messagename。
不同語言的實現(xiàn)會支持動態(tài)庫以線程安全的方式去幫助封裝或者解封裝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類型的動態(tài)庫仍在開發(fā)之中
如果你已經(jīng)很熟悉proto2語法,使用Any替換拓展
Oneof
如果你的消息中有很多可選字段, 并且同時至多一個字段會被設(shè)置, 你可以加強這個行為,使用oneof特性節(jié)省內(nèi)存.
Oneof字段就像可選字段, 除了它們會共享內(nèi)存, 至多一個字段會被設(shè)置。 設(shè)置其中一個字段會清除其它字段。 你可以使用case()或者WhichOneof() 方法檢查哪個oneof字段被設(shè)置, 看你使用什么語言了.
使用Oneof
為了在.proto定義Oneof字段, 你需要在名字前面加上oneof關(guān)鍵字, 比如下面例子的test_oneof:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后你可以增加oneof字段到 oneof 定義中. 你可以增加任意類型的字段, 但是不能使用repeated 關(guān)鍵字.
在產(chǎn)生的代碼中, oneof字段擁有同樣的 getters 和setters, 就像正常的可選字段一樣. 也有一個特殊的方法來檢查到底那個字段被設(shè)置. 你可以在相應(yīng)的語言API指南中找到oneof API介紹.
Oneof 特性
- 設(shè)置oneof會自動清楚其它oneof字段的值. 所以設(shè)置多次后,只有最后一次設(shè)置的字段有值.
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++,需確保代碼不會導(dǎo)致內(nèi)存泄漏. 下面的代碼會崩潰, 因為
sub_message已經(jīng)通過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());
向后兼容性問題
當(dāng)增加或者刪除oneof字段時一定要小心. 如果檢查oneof的值返回None/NOT_SET, 它意味著oneof字段沒有被賦值或者在一個不同的版本中賦值了。 你不會知道是哪種情況,因為沒有辦法判斷如果未識別的字段是一個oneof字段。
Tage 重用問題:
- 將字段移入或移除oneof:在消息被序列號或者解析后,你也許會失去一些信息(有些字段也許會被清除)
- 刪除一個字段或者加入一個字段:在消息被序列號或者解析后,這也許會清除你現(xiàn)在設(shè)置的oneof字段
- 分離或者融合oneof:行為與移動常規(guī)字段相似。
Map(映射)
如果你希望創(chuàng)建一個關(guān)聯(lián)映射,protocol buffer提供了一種快捷的語法:
map<key_type, value_type> map_field = N;
其中key_type可以是任意Integer或者string類型(所以,除了floating和bytes的任意標(biāo)量類型都是可以的)value_type可以是任意類型。
例如,如果你希望創(chuàng)建一個project的映射,每個Projecct使用一個string作為key,你可以像下面這樣定義:
map<string, Project> projects = 3;
- Map的字段可以是repeated。
- 序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map
- 當(dāng)為.proto文件產(chǎn)生生成文本格式的時候,map會按照key 的順序排序,數(shù)值化的key會按照數(shù)值排序。
- 從序列化中解析或者融合時,如果有重復(fù)的key則后一個key不會被使用,當(dāng)從文本格式中解析map時,如果存在重復(fù)的key。
生成map的API現(xiàn)在對于所有proto3支持的語言都可用了,你可以從API指南找到更多信息。
向后兼容性問題
map語法序列化后等同于如下內(nèi)容,因此即使是不支持map語法的protocol buffer實現(xiàn)也是可以處理你的數(shù)據(jù)的:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
包
當(dāng)然可以為.proto文件新增一個可選的package聲明符,用來防止不同的消息類型有命名沖突。如:
package foo.bar;
message Open { ... }
在其他的消息格式定義中可以使用包名+消息名的方式來定義域的類型,如:
message Foo {
...
required foo.bar.Open open = 1;
...
}
包的聲明符會根據(jù)使用語言的不同影響生成的代碼。
- 對于C++,產(chǎn)生的類會被包裝在C++的命名空間中,如上例中的
Open會被封裝在foo::bar空間中; - 對于Java,包聲明符會變?yōu)閖ava的一個包,除非在.proto文件中提供了一個明確有java_package; - 對于 Python,這個包聲明符是被忽略的,因為Python模塊是按照其在文件系統(tǒng)中的位置進(jìn)行組織的。
- 對于Go,包可以被用做Go包名稱,除非你顯式的提供一個
option go_package在你的.proto文件中。 - 對于Ruby,生成的類可以被包裝在內(nèi)置的Ruby名稱空間中,轉(zhuǎn)換成Ruby所需的大小寫樣式 (首字母大寫;如果第一個符號不是一個字母,則使用PB_前綴),例如
Open會在Foo::Bar名稱空間中。 - 對于javaNano包會使用Java包,除非你在你的文件中顯式的提供一個
option java_package。 - 對于C#包可以轉(zhuǎn)換為
PascalCase后作為名稱空間,除非你在你的文件中顯式的提供一個option csharp_namespace,例如,Open會在Foo.Bar名稱空間中
包及名稱的解析
Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內(nèi)部開始查找,依次向外進(jìn)行,每個包會被看作是其父類包的內(nèi)部類。當(dāng)然對于 (foo.bar.Baz)這樣以“.”分隔的意味著是從最外圍開始的。
ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。 對于不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規(guī)則。
定義服務(wù)(Service)
如果想要將消息類型用在RPC(遠(yuǎn)程方法調(diào)用)系統(tǒng)中,可以在.proto文件中定義一個RPC服務(wù)接口,protocol buffer編譯器將會根據(jù)所選擇的不同語言生成服務(wù)接口代碼及存根。如,想要定義一個RPC服務(wù)并具有一個方法,該方法能夠接收 SearchRequest并返回一個SearchResponse,此時可以在.proto文件中進(jìn)行如下定義:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
最直觀的使用protocol buffer的RPC系統(tǒng)是gRPC一個由谷歌開發(fā)的語言和平臺中的開源的PRC系統(tǒng),gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接為您從.proto文件中產(chǎn)生相關(guān)的RPC代碼。
如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC實現(xiàn),你可以從proto2語言指南中找到更多信息
還有一些第三方開發(fā)的PRC實現(xiàn)使用Protocol Buffer。參考第三方插件wiki查看這些實現(xiàn)的列表。
JSON 映射
Proto3 支持JSON的編碼規(guī)范,使他更容易在不同系統(tǒng)之間共享數(shù)據(jù),在下表中逐個描述類型。
如果JSON編碼的數(shù)據(jù)丟失或者其本身就是,這個數(shù)據(jù)會在解析成protocol buffer的時候被表示成默認(rèn)值。如果一個字段在protocol buffer中表示為默認(rèn)值,體會在轉(zhuǎn)化成JSON的時候編碼的時候忽略掉以節(jié)省空間。具體實現(xiàn)可以提供在JSON編碼中可選的默認(rèn)值。
| proto3 | JSON | JSON示例 | 注意 |
|---|---|---|---|
| message | object | {“fBar”: v, “g”: null, …} | 產(chǎn)生JSON對象,消息字段名可以被映射成lowerCamelCase形式,并且成為JSON對象鍵,null被接受并成為對應(yīng)字段的默認(rèn)值 |
| enum | string | “FOO_BAR” | 枚舉值的名字在proto文件中被指定 |
| map | object | {“k”: v, …} | 所有的鍵都被轉(zhuǎn)換成string |
| repeated V | array | [v, …] | null被視為空列表 |
| bool | true, false | true, false | |
| string | string | “Hello World!” | |
| bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
| int32, fixed32, uint32 | number | 1, -10, 0 | JSON值會是一個十進(jìn)制數(shù),數(shù)值型或者string類型都會接受 |
| int64, fixed64, uint64 | string | “1”, “-10” | JSON值會是一個十進(jìn)制數(shù),數(shù)值型或者string類型都會接受 |
| float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON值會是一個數(shù)字或者一個指定的字符串如”NaN”,”infinity”或者”-Infinity”,數(shù)值型或者字符串都是可接受的,指數(shù)符號也可以接受 |
| Any | object | {“@type”: “url”, “f”: v, … } | 如果一個Any保留一個特上述的JSON映射,則它會轉(zhuǎn)換成一個如下形式:{"@type": xxx, "value": yyy}否則,該值會被轉(zhuǎn)換成一個JSON對象,@type字段會被插入所指定的確定的值 |
| Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用RFC 339,其中生成的輸出將始終是Z-歸一化啊的,并且使用0,3,6或者9位小數(shù) |
| Duration | string | “1.000340012s”, “1s” | 生成的輸出總是0,3,6或者9位小數(shù),具體依賴于所需要的精度,接受所有可以轉(zhuǎn)換為納秒級的精度 |
| Struct | object | { … } | 任意的JSON對象,見struct.proto |
| Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包裝器在JSON中的表示方式類似于基本類型,但是允許nulll,并且在轉(zhuǎn)換的過程中保留null |
| FieldMask | string | “f.fooBar,h” | 見fieldmask.proto |
| ListValue | array | [foo, bar, …] | |
| Value | value | 任意JSON值 | |
| NullValue | JSON null |
選項
在定義.proto文件時能夠標(biāo)注一系列的options。Options并不改變整個文件聲明的含義,但卻能夠影響特定環(huán)境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。
一些選項是文件級別的,意味著它可以作用于最外范圍,不包含在任何消息內(nèi)部、enum或服務(wù)定義中。一些選項是消息級別的,意味著它可以用在消息定義的內(nèi)部。當(dāng)然有些選項可以作用在域、enum類型、enum值、服務(wù)類型及服務(wù)方法中。到目前為止,并沒有一種有效的選項能作用于所有的類型。
如下就是一些常用的選擇:
-
java_package(文件選項) :這個選項表明生成java類所在的包。如果在.proto文件中沒有明確的聲明java_package,就采用默認(rèn)的包名。當(dāng)然了,默認(rèn)方式產(chǎn)生的 java包名并不是最好的方式,按照應(yīng)用名稱倒序方式進(jìn)行排序的。如果不需要產(chǎn)生java代碼,則該選項將不起任何作用。如:
option java_package = "com.example.foo";
-
java_outer_classname(文件選項): 該選項表明想要生成Java類的名稱。如果在.proto文件中沒有明確的java_outer_classname定義,生成的class名稱將會根據(jù).proto文件的名稱采用駝峰式的命名方式進(jìn)行生成。如(foo_bar.proto生成的java類名為FooBar.java),如果不生成java代碼,則該選項不起任何作用。如:
option java_outer_classname = "Ponycopter";
-
optimize_for(文件選項): 可以被設(shè)置為 SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值將通過如下的方式影響C++及java代碼的生成:-
SPEED (default): protocol buffer編譯器將通過在消息類型上執(zhí)行序列化、語法分析及其他通用的操作。這種代碼是最優(yōu)的。 -
CODE_SIZE: protocol buffer編譯器將會產(chǎn)生最少量的類,通過共享或基于反射的代碼來實現(xiàn)序列化、語法分析及各種其它操作。采用該方式產(chǎn)生的代碼將比SPEED要少得多, 但是操作要相對慢些。當(dāng)然實現(xiàn)的類及其對外的API與SPEED模式都是一樣的。這種方式經(jīng)常用在一些包含大量的.proto文件而且并不盲目追求速度的 應(yīng)用中。 -
LITE_RUNTIME: protocol buffer編譯器依賴于運行時核心類庫來生成代碼(即采用libprotobuf-lite 替代libprotobuf)。這種核心類庫由于忽略了一 些描述符及反射,要比全類庫小得多。這種模式經(jīng)常在移動手機平臺應(yīng)用多一些。編譯器采用該模式產(chǎn)生的方法實現(xiàn)與SPEED模式不相上下,產(chǎn)生的類通過實現(xiàn) MessageLite接口,但它僅僅是Messager接口的一個子集。
-
option optimize_for = CODE_SIZE;
-
cc_enable_arenas(文件選項):對于C++產(chǎn)生的代碼啟用arena allocation -
objc_class_prefix(文件選項):設(shè)置Objective-C類的前綴,添加到所有Objective-C從此.proto文件產(chǎn)生的類和枚舉類型。沒有默認(rèn)值,所使用的前綴應(yīng)該是蘋果推薦的3-5個大寫字符,注意2個字節(jié)的前綴是蘋果所保留的。 -
deprecated(字段選項):如果設(shè)置為true則表示該字段已經(jīng)被廢棄,并且不應(yīng)該在新的代碼中使用。在大多數(shù)語言中沒有實際的意義。在java中,這回變成@Deprecated注釋,在未來,其他語言的代碼生成器也許會在字標(biāo)識符中產(chǎn)生廢棄注釋,廢棄注釋會在編譯器嘗試使用該字段時發(fā)出警告。如果字段沒有被使用你也不希望有新用戶使用它,嘗試使用保留語句替換字段聲明。
int32 old_field = 6 [deprecated=true];
自定義選項
ProtocolBuffers允許自定義并使用選項。該功能應(yīng)該屬于一個高級特性,對于大部分人是用不到的。如果你的確希望創(chuàng)建自己的選項,請參看 Proto2 Language Guide。注意創(chuàng)建自定義選項使用了拓展,拓展只在proto3中可用。
生成訪問類
可以通過定義好的.proto文件來生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代碼,需要基于.proto文件運行protocol buffer編譯器protoc。如果你沒有安裝編譯器,下載安裝包并遵照README安裝。對于Go,你還需要安裝一個特殊的代碼生成器插件。你可以通過GitHub上的protobuf庫找到安裝過程
通過如下方式調(diào)用protocol編譯器:
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 --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
IMPORT_PATH聲明了一個.proto文件所在的解析import具體目錄。如果忽略該值,則使用當(dāng)前目錄。如果有多個目錄則可以多次調(diào)用--proto_path,它們將會順序的被訪問并執(zhí)行導(dǎo)入。-I=IMPORT_PATH是--proto_path的簡化形式。-
當(dāng)然也可以提供一個或多個輸出路徑:
-
--cpp_out在目標(biāo)目錄DST_DIR中產(chǎn)生C++代碼,可以在C++代碼生成參考中查看更多。 -
--java_out在目標(biāo)目錄DST_DIR中產(chǎn)生Java代碼,可以在 Java代碼生成參考中查看更多。 -
--python_out在目標(biāo)目錄 DST_DIR 中產(chǎn)生Python代碼,可以在Python代碼生成參考中查看更多。 -
--go_out在目標(biāo)目錄 DST_DIR 中產(chǎn)生Go代碼,可以在GO代碼生成參考中查看更多。 -
--ruby_out在目標(biāo)目錄 DST_DIR 中產(chǎn)生Go代碼,參考正在制作中。 -
--javanano_out在目標(biāo)目錄DST_DIR中生成JavaNano,JavaNano代碼生成器有一系列的選項用于定制自定義生成器的輸出:你可以通過生成器的README查找更多信息,JavaNano參考正在制作中。 -
--objc_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在Objective-C代碼生成參考中查看更多。 -
--csharp_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在C#代碼生成參考中查看更多。 -
--php_out在目標(biāo)目錄DST_DIR中產(chǎn)生Object代碼,可以在PHP代碼生成參考中查看更多。
-
作為一個方便的拓展,如果DST_DIR以.zip或者.jar結(jié)尾,編譯器會將輸出寫到一個ZIP格式文件或者符合JAR標(biāo)準(zhǔn)的.jar文件中。注意如果輸出已經(jīng)存在則會被覆蓋,編譯器還沒有智能到可以追加文件。
- 你必須提議一個或多個.proto文件作為輸入,多個.proto文件可以只指定一次。雖然文件路徑是相對于當(dāng)前目錄的,每個文件必須位于其IMPORT_PATH下,以便每個文件可以確定其規(guī)范的名稱。
轉(zhuǎn)載: https://blog.csdn.net/hulinku/article/details/80827018