Protocol Buffer學習教程之語法手冊(二)


1.說明

此嚮導介紹如何使用protocol buffer language創建一個自己的protocolbuffer文件,包括語法與如何通過“.proto”文件生成數據訪問的類,此處只介紹proto2proto3的更多消息點這裏

這是一個參考指南,一步一步功能描述的示例,請訪問以下鏈接,並選擇你自己熟悉的開發語言

2.定義消息類型

首先我們來看一個簡單的示例,定義一個searchrequest消息格式,每一個search request有一個query字符串,頁碼,每頁結果數量。以下是定義的“.proto”文件:

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

消息指定了三“段”(“名-值”對),每一段,有修飾符、類型、名稱、編號組成,還會有一些可選項組成,如指定缺省值呀,後續章節中會介紹到。

段類型

在以上的示例中,所有的段都指定了類型。你也可以用複合類型,包括枚舉與其他消息類型(protobuffer 定義的類型)

分配標記

從以上示例中可以看到,每一段都指定了唯一編號“= x”,它用於二進制格式中標記“段”,當你的消息類型投入使用後,它們的順序不能改變。編碼時編號在1~15區間內的編號佔用一個字節,在16~2047區間用兩個字節,所以,你可以保留1~15的編號給那些比較常用的元素使用,併爲將來可能要增加的段預留一些此區間的編號。

最小編號爲1,最大編號爲229- 1,或者536,870,911,但是19000 ~ 19999區間的編號是保留給Protocol Buffers使用的。

修飾符

required: 一個格式完好的消息必須最少有一個這種類型的段。被這種修飾符修飾的段,是必須賦值的,否則會被認爲“未初始化”,如果未賦值,在debug版本序列化時會拋出斷言錯誤,release版本能順利通過,但是反序列化(解析)時,必然會失敗的。除此之外,requiredoptional修飾類型就沒有什麼區別了。

optional: 一個格式完好的消息有N(N0)個這種類型的段。對於此字段的賦值,不是必須的。如果沒有賦值,它將使用默認值,對於缺省數據類型,你可以指定默認值,如僞代碼中的phone number,如果沒有指定默認值,將使用系統默認值,數字類型爲0string爲空,bool型爲false。對於嵌套類型,默認值爲“默認實例”或“原型”。

repeated: 字段會出現N(0)次,重複的值將按順序保存在“protocol buffer”中,你只要把它當成一個動態數組即可。

由於歷史原因,repeated修飾的段的數據類型如果是數字類型的話,不能高效編碼,爲提高效果可以使用一個選項[packed=true]來獲得更高的效率,示例如:

repeated int32samples = 4 [packed=true];

關於packed參見這裏

Required 是永久的,使用此種修飾符時,要特別小心,當你不想給此種類型的字段賦值的話,你需要把它改成Optional類型,它可能會出現一些問題----接受方可能會認爲此消息是非完事的,而拒絕解析。有些google開發者認爲required利大於弊,所以他們更喜歡使用optionalrepeated。當然,這種觀點不一定是普遍的。

定義多個消息

可以在一個“.proto”文件定義多個消息,特別是對那些有相互關聯的消息,比較適用。如你需要給以上示例的請求消息加一個響應消息

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.
}

保留段

如果你要對以前定義的消息中的段刪除,或者註釋。將來使用者可能會更新他們的消息,並重新使用這些段,或者他們又使用此消息的舊版本,這將導致數據損壞,隱性錯誤等問題,有一個辦法可以避免這些問題。把這個刪除的段指定爲reserved類型,可以通過它的標誌指定,也可以通過名稱(JSON版本會有問題)指定,指定後使用都如果再使用這些段,將會收到錯誤提醒。使用reserved時,同一行,不能混合使用標誌與名稱。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

.proto生成的內容

通過protocol buffer 編譯器對.proto文件進行編譯後,能生成你選擇的語言的代碼。你可以通過此代碼對你在.proto文件中描述的數據進行提取、給段賦值、把你打包後的數據序列化成流、把接收到的流反序列化成類實例等操作。

C++:對應每一個.proto文件生成.h.cpp文件。每個消息將生成一個類。可以通過此鏈接,找到對應語言的API

3.數據類型

以下列表中是.proto文件中數據類型與相應的語言之間的數據類型的對應關係。

.proto Type

Notes

C++ Type

Java Type

Python Type[2]

Go Type

double

double

double

float

*float64

float

float

float

float

*float32

int32

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.

int32

int

int

*int32

int64

Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.

int64

long

int/long[3]

*int64

uint32

Uses variable-length encoding.

uint32

int[1]

int/long[3]

*uint32

uint64

Uses variable-length encoding.

uint64

long[1]

int/long[3]

*uint64

sint32

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.

int32

int

int

*int32

sint64

Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.

int64

long

int/long[3]

*int64

fixed32

Always four bytes. More efficient than uint32 if values are often greater than 228.

uint32

int[1]

int

*uint32

fixed64

Always eight bytes. More efficient than uint64 if values are often greater than 256.

uint64

long[1]

int/long[3]

*uint64

sfixed32

Always four bytes.

int32

int

int

*int32

sfixed64

Always eight bytes.

int64

long

int/long[3]

*int64

bool

bool

boolean

bool

*bool

string

A string must always contain UTF-8 encoded or 7-bit ASCII text.

string

String

str/unicode[4]

*string

bytes

May contain any arbitrary sequence of bytes.

string

ByteString

str

[]byte

關於以上數據類型的編碼方式的詳情,點擊這裏

[1]Java, unsigned 32-bit and64-bit被解釋成有符合整形,最高位被描述成符號位。

[2]對段進行賦值時,會執行類型檢查。

[3]64-bit orunsigned 32-bit 整形被在解析時都被解析成long型,可以爲int型,如果在設置的時候設置成int型的話。總之,值必須與設置的時候一致。參見[2]

[4]Pythonstrings將解析爲寬字符,同時可以是ASCII,當被指定爲ASCII的話(主觀指定)

4.可選字段與缺省值

消息中的元素可以指定爲optional類型,指此段可以不被賦值,在解析時,沒有被賦值的段將被賦缺省值。

缺省值可以在字段描述時指定,如給字段指定一個爲10的缺省值

optional int32 result_per_page = 3 [default = 10];

沒有指定缺省值的optional類型,解析時將被賦類型相關的缺省值。string爲空,foolsfalse,numberic0enmums爲枚舉中的第一個值。

5.枚舉類型

枚舉類型,大家都懂了,不多說。下面是爲消息加一個Corpus枚舉類型,以下是示例,定義的時候的數據類型應該是Corpus,而不是整形哦。

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  optional Corpus corpus = 4 [default = UNIVERSAL];
}

枚舉常量的名稱應該是唯一的,如果想要在不同的枚舉中用相同的名稱,則要指定一個選項allow_alias option 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.
}

個人建議採用,常量名稱加枚舉名稱爲前綴,儘量不要重名,省去一些麻煩。

6.自定義數據類型

大家都應該能看明白,就是消息類型中定義自定義類型,不過,他們應該在同一個”.proto”文件下,不然要引入,關於引用見下一章節。

message SearchResponse {
  repeated Result result = 1;
}
 
message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

引用自定義類型

不在同一個消息文件的消息,可以通過import相互引用,只要在引用的文件裏,加上以下語句:

import "myproject/other_protos.proto";

有時候,你想把一個文件移到一個新的目錄下,但是,如果你移動此文件的話,你需要修改所有與此文件相關的文件的引用路徑,這可麻煩了,那怎麼辦呢,你可以把原路徑下的文件保留,然後在原路徑下的文件加上import public語法,指引所有引用此文件的文件,必須引用新文件,這樣所有與舊文件相關的文件,將會自動引用新目錄下的文件,示例如下:

// 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";
// You use definitions from old.proto and new.proto, but not other.proto

編譯器將要在你編譯指定的路徑----通過-I=proto_path指定的路徑下搜索引用文件,如果沒有指定此參數,將在編譯器所在目錄下搜索。一般情況下,你需要通過-I=proto_path指定路徑。如下:

proto –I=proto文件路徑 –cpp_out=proto文件目錄 proto文件路徑

proto3消息類型

可以引用proto3版本的消息類型到proto2版本,反之亦然,但是,proto2的枚舉類型不適用於proto3版本的語法。

6.嵌套類型

消息是可以嵌套的,當一個消息需要使用另外一個消息裏面的消息時,你可以加上其“父”消息域即可,示例如一:

message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}
message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

同時你可以進行多層嵌套,如下:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      required int64 ival = 1;
      optional bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      required int32 ival = 1;
      optional bool  booly = 2;
    }
  }
}

注意此功能已經被棄用,在定義一個新的消息時,不建議再使用它,應該使用嵌套消息來替代它。組是消息嵌套中的另一種方法,如在SearchResponse嵌套一個Result消息:

message SearchResponse {
  repeated group Result = 1 {
    required string url = 2;
    optional string title = 3;
    repeated string snippets = 4;
  }
}

7.更新消息

如果一個消息因爲需求需要進行修改時,只要遵循以下規則,它就可以不影響原來的代碼的基礎上進行升級修改。

不要修改已經存在的段的編號。

在舊消息中,新增段時,最好使用optionalrepeated修飾符,這樣那些舊的消息格式文件也能新版本的消息,只要required元素都被賦值了的話。應該給新增的元素加上缺省值,這樣新定義的消息格式將能與舊的消息格式進行一定的交互,同理,新的消息數據能被舊的消息數據解析。

對於舊消息來說,新增的段是不可識別的,但是新增的段並不會被丟棄,並能被序列化,如果被新的消息解析的話,它將正確解析。

required段是可以被刪除的,只要它的編號不會再被使用。如果你想對它重命名,請加上"OBSOLETE_"前綴,或者直接對這個編號設置成reserved,這樣就能避免你的“.proto”文件的使用者使用它。

一個非required字段可以轉化爲一個擴展(extension),反之亦然(擴展可以轉化爲一個非required字段),只要編號與類型不變。

int32,uint32,int64,uint64,and bool這些類型是可以相互轉換的,同時還能保證向前向後兼容。解析的時候,如果不符的話,它將像C++裏的強制轉換一樣(64位整數,被強制轉換成32)

sint32 sint64 是相互兼容的,但是與其他類型的整形不兼容。

stringbytes是相互兼容的,只要bytes是有效的UTF-8編碼。

嵌套消息與bytes是兼容的,只要bytes包含該消息已經編碼過的版本。

fixed32sfixed32兼容,同時ixed64sfixed64兼容。

optionalrepeated兼容,如果一個序列化的數據串,被使用者預判爲optional類型的話,如果它是缺省數據類型的話,將解析最後一個值(repeated類型可能會有很多值),如果是消息類型(自定義消息)的話,將被全部解析。

修改一個字段的缺省值一般不會有問題的,接收者收到一個某個段沒有賦值的消息時,接收者是按自己的消息版本的缺省值給它賦值,而不是發送者的版本的缺省值。

枚舉類型與int32,uint32,int64,and uint64類型是兼容的,當然如果溢出的話,它可能被截斷,但是,需要注意的是,客戶端(使用者,接收者)對他們會區別對待,當反序列化時,不能識別的枚舉常量將被丟棄,並會得到“has…”之類的提示,並返回枚舉中第一個常量給它賦值,或者是缺省值,如果有指定缺省值的話。

8.擴展

在消息中聲明一個號段預留給第三方來定義,其他使用者可以在你指定的這個號段裏定義他們自己的消息文件,同時不需要重新編譯原始文件,如例:

message Foo {
  // ...
  extensions 100 to 199;
}

也就是說[100, 199]之間的號段被保留爲擴展所用,如例:

extend Foo {
  optional int32 bar = 126;
}

也就是說消息中多了一個bar的段,當對它進行編碼時,在棧格式上與你重新定義一個這樣的bar字段是無異的,訪問此擴展段的訪問標準段的方式很類似,編譯器給生成了擴展段的交互方法,給擴展段賦值(C++),如例:

Foo foo;
foo.SetExtension(bar,15);

類似的,Foo類還下定義了以下接口:

HasExtension(),ClearExtension(),GetExtension(),MutableExtension(), and AddExtension()

關於擴展段的更多信息,請參考你選擇的對應語言的代碼生成手冊,擴展段可以是任何數據類型的段,除oneofsmap外。

嵌套擴展

可以聲明一個擴展,在其他消息類型裏面:

message Baz {
  extend Foo {
    optional int32 bar = 126;
  }
  ...
}

C++中,訪問擴展示例如下:

Foo foo;
foo.SetExtension(Baz::bar, 15);

換句話說,這唯一能說明的是,foo擴展定義在bar消息裏面

這一般是引起混淆的根源:聲明一個擴展塊,並嵌套在一個消息裏,同時此消息與擴展並沒有任何關係。以上示例並沒有表明barzFoo的子類型,唯一隻表示了bar被聲明在Baz消息內,它只是一個簡單的靜態成員而已。

一般常用的方法是,把擴展定義在擴展消息裏面,如定義一個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;
}

事實上這種語法可以比較完美的避免困惑,而上面相互嵌套的語法通常會讓人產生他們之間有子類化的誤解,特別對那些對擴展不是很熟悉的用戶。

選擇擴展編號

確保兩個用戶不會在同一個消息中使用相同編號來擴展消息,不然會因爲數據類型不一樣可能引發數據損壞。可以約定擴展的編號範圍來解決這個問題(譯者注:但是我也沒有看懂怎麼解決這個問題),如例:

message Foo {
  extensions 1000 to max;
}

max229 - 1,536,870,911[19000,19999]號段是保留給ProtocolBuffers實現使用的,此號段不能用。

9.Oneof(Union)

Oneof其實就是C/C++中的Union共用體,當你某個消息裏,有很多optional屬性的段,同時他們當中同時最多隻有一個需要賦值的時候,它們可以共用內存,此功能叫Oneof。可以給所有的段賦值,但是你給其中一個段賦值時,其他段的值自動被清空,你可以通過case()WhichOneof()方法來檢查哪個段被賦值,取決於你使用的語言。

Oneof用法

以下是語法,用oneof關鍵字後面跟着oneof類型名,如例:

message SampleMessage{
oneof test_oneof {
      string name =4;
      SubMessage sub_message =9;
  }
}

oneof類型(test_oneof)裏可以加任何數據類型的段,當然不能加任何修飾符。在你生成的代碼時,oneof段有相同的getterssetters方法,同時有特定的方法用於判斷哪個段被賦了值,更多關於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());

If the parser encounters multiple members of the same oneof onthe wire, only the last member seen is used in the parsed message.

oneof不支持擴展

oneof不支持repeated修飾

反射APIsoneof有效

如果你使用的是C++語言,請注意內存引起的衝突,如下例中的衝突是因爲內存已經刪除引起的。

SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here

·        Again in C++, if you Swap() two messages with oneofs, each message will end up with theother’s oneof case: in the example below, msg1 will have a sub_message and msg2 will have a 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中移入或者移出一些optinal段,可以丟失一些信息(某些段將被清空)

當消息已經序列化或者反序列化後,刪除或者重新恢復某些段,它可能會清除當前設置的某些段。

·        Split or merge oneof: This hassimilar issues to moving regular optional fields.

10.Maps

MapC++中的映射,以下是定義映射類型的語法:

map<key_type, value_type> map_field = N;

key_type可以是整數字符串類型,value_type可以爲任何類型,如定義prOjects的映射表,鍵爲string,如例:

map<string,Project> projects =3;

生成的APIproto2版本全支持,更詳細的消息參見連接

Maps功能

不支持擴展

不能被repeated,optional, or required修飾

Wire format ordering and map iteration ordering of map values isundefined, so you cannot rely on your map items being in a particular order.(對於值與鍵的排序並沒有定義,所以不能把你的迭代順序依賴於此)

When generating text format for a .proto, maps are sorted bykey. Numeric keys are sorted numerically.(通過.proto文件生成文件格式時,是按鍵的以數字排序)

When parsing from the wire or when merging, if there areduplicate map keys the last key seen is used. When parsing a map from textformat, parsing will fail if there are duplicate keys.(當反序列化或者融合Map時,如果有重新的key將以最後一個爲準,如果通過文件格式反序列化,如果有重複的鍵,將會失敗)

向後兼容

Map也可以通過以下方法來實現,所以protobuf並不保證以後都支持map的語法:

message MapFieldEntry{
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

11.Packages(命名空間)

package爲了防止命名衝突的關鍵字,功能與namespace類似。如例:

package foo.bar;
message Open { ... }

定義消息時,可以通過package名來指定域,如例:

message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

package的效果,依賴於你選擇的語言:

對於C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中;

對於Java,包聲明符會變爲java的一個包,除非在.proto文件中提供了一個明確有java_package

對於 Python,這個包聲明符是被忽略的,因爲Python模塊是按照其在文件系統中的位置進行組織的。

包及名稱解析

Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每個包會被看作是其父類包的內部類。當然對於(foo.bar.Baz)這樣以“.”分隔的意味着是從最外圍開始的。ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。對於不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規則。

12.定義服務接口

如果你想要把你的消息用於遠程調用系統,那麼你可以在proto文件中定義服務接口,然後protobuf編譯器可以生成服務接口代碼及存根(未知其意),例如,你想要定義一個服務接口即能接受SearchRequest請求,同時能返回SearchResponse結果,你可以如此定義你的proto文件:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

proto編譯器將生成一個SearchService的抽象接口與一個相應的存根實現,存根將把所有請求都指向RpcChannel,它是一個抽象接口,同時你需要對它具體實現,如序列化消息並把通過http發給另一個服務端,換句話說,它只爲基於proto遠程調用提供一個數據類型安排的接口,並沒有提供具體的實現,在C++中,你可以如下實現:

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

voidDoSearch(){
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = newMyRpcChannel("somehost.example.com:1234");
  controller = newMyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given above.
  service = newSearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, request, response, protobuf::NewCallback(&Done));
}

voidDone(){
  delete service;
  delete channel;
  delete controller;
}

所有service類都必須實現Service接口,它提供了一種用來調用具體方法的方式,即在編譯期不需要知道方法名及它的輸入、輸出類型。在服務器端,通過服務註冊它可以被用來實現一個RPC Server

using google::protobuf;

classExampleSearchService:publicSearchService{
 public:
  voidSearch(protobuf::RpcController* controller,
              constSearchRequest* request,
              SearchResponse* response,
              protobuf::Closure*done){
    if(request->query()=="google"){
      response->add_result()->set_url("http://www.google.com");
    } elseif(request->query()=="protocol buffers"){
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main(){
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service =newExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return0;
}

如果你不想把它加入到你現有的遠程調用服務系統的話,你可以使用gRPC:google開發的一種跨語言跨平臺的開源系統,gRPCprotocolbuffer運行的非常出色,並能通過一個專門的protocolbuffer 編譯器插件,結合proto文件直接生成RPC相關的代碼,但是在版本proto2proto3,在客戶端與服務端之間有一些兼容性問題,所以我建議你用proto3定義gRPC服務,關於proto3的更多多語法請參考Proto3,實現gRPC,那你需要高於3.0.0()版本的protocol buffers編譯器與庫。

另外,還有一些第三方團隊在給RPC提供實現,下面是我們已知的鏈接列表:third-party add-ons wiki page

13.選項(Options)/後續章節待翻譯

proto文件中,某些個別的聲明會有很多選項,這些聲明並不是用於完全改變聲明的含義,而是用於影響其在特定的上下文中處理方式,完整的選項列定義在文件google/protocuf/descriptor.proto中。

某些選項是文件級的,級別比較高,不同於具體消息、枚舉或者服務定義的規範,而有些選項是消息級別的,意思是它們用於消息定義,而有些選項是段級別的,是用於定義具體的段的。選項也能用於枚舉類型,枚舉值,服務類型各服務方法,但是,當目前爲止關沒有任何一個有效的選項,能同時滿足所有的類型。

下面介紹一些常用的選項:

java_package(文件級別):這個選項表明生成java類所在的包。如果在.proto文件中沒有明確的聲明java_package,就採用默認的包名。當然了,默認方式產生的 java包名並不是最好的方式,按照應用名稱倒序方式進行排序的。如果不需要產生java代碼,則該選項將不起任何作用。

option java_package = "com.example.foo";

·        java_outer_classname (fileoption): The class name for the outermost Java class (and hence the file name)you want to generate. If no explicit java_outer_classname is specified in the .proto file, the class name will be constructed by converting the .proto file name to camel-case (so foo_bar.proto becomesFooBar.java). If not generating Java code, this option has no effect.

option java_outer_classname = "Ponycopter";

·        optimize_for (fileoption): Can be set to SPEEDCODE_SIZE, or LITE_RUNTIME. This affects the C++and Java code generators (and possibly third-party generators) in the followingways:

·        SPEED (default):The protocol buffer compiler will generate code for serializing, parsing, andperforming other common operations on your message types. This code isextremely highly optimized.

·        CODE_SIZE: The protocol buffer compiler will generate minimal classes andwill rely on shared, reflection-based code to implement serialialization,parsing, and various other operations. The generated code will thus be muchsmaller than with SPEED, but operations will beslower. Classes will still implement exactly the same public API as they do in SPEED mode. This mode is most useful in apps that contain a very largenumber .proto files and do not need all of them to be blindingly fast.

·        LITE_RUNTIME: The protocol buffer compiler will generate classes that dependonly on the "lite" runtime library (libprotobuf-lite instead of libprotobuf). The lite runtime ismuch smaller than the full library (around an order of magnitude smaller) butomits certain features like descriptors and reflection. This is particularlyuseful for apps running on constrained platforms like mobile phones. Thecompiler will still generate fast implementations of all methods as it does in SPEED mode. Generated classes will only implement the MessageLite interface in each language, which provides only a subset of themethods of the full Message interface.

option optimize_for = CODE_SIZE;

·        cc_generic_servicesjava_generic_servicespy_generic_services (file options): Whether or not the protocol buffer compilershould generate abstract service code based on services definitions in C++, Java, and Python, respectively. For legacy reasons,these default to true. However, as of version2.3.0 (January 2010), it is considered preferrable for RPC implementations toprovide code generator plugins to generate code more specific to each system, rather than relyon the "abstract" services.

·         // 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 (fileoption): Enables arena allocation for C++ generated code.

·        message_set_wire_format (message option): If set to true, the message uses adifferent binary format intended to be compatible with an old format usedinside Google called MessageSet. Users outside Googlewill probably never need to use this option. The message must be declaredexactly as follows:

·         message Foo {
·           option message_set_wire_format = true;
·           extensions 4 to max;
·         }

·        packed (fieldoption): If set to true on a repeated field of a basic numeric type, a more compact encoding is used. There is no downside to using this option. However,note that prior to version 2.3.0, parsers that received packed data when notexpected would ignore it. Therefore, it was not possible to change an existingfield to packed format without breaking wire compatibility. In 2.3.0 and later,this change is safe, as parsers for packable fields will always accept bothformats, but be careful if you have to deal with old programs using oldprotobuf versions.

repeated int32 samples = 4 [packed=true];

·        deprecated (fieldoption): If set to true, indicates that thefield is deprecated and should not be used by new code. In most languages thishas no actual effect. In Java, this becomes a @Deprecated annotation. In the future, other language-specific codegenerators may generate deprecation annotations on the field's accessors, whichwill in turn cause a warning to be emitted when compiling code which attemptsto use the field. If the field is not used by anyone and you want to preventnew users from using it, consider replacing the field declaration with a reserved statement.

optional int32 old_field = 6 [deprecated=true];

Custom Options

Protocol Buffers even allow you to define and use your ownoptions. Note that this is an advancedfeature which most people don'tneed. Since options are defined by the messages defined in google/protobuf/descriptor.proto(like FileOptions or FieldOptions), defining your own options is simply a matter of extending those messages. For example:

import "google/protobuf/descriptor.proto";
 
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
 
message MyMessage {
  option (my_option) = "Hello world!";
}

Here we have defined a new message-level option by extending MessageOptions. When we then use the option, the option name must be enclosedin parentheses to indicate that it is an extension. We can now read the valueofmy_option in C++ like so:

string value = MyMessage::descriptor()->options().GetExtension(my_option);

Here, MyMessage::descriptor()->options() returns the MessageOptions protocol message for MyMessage. Reading custom options from it is justlike reading any other extension.

Similarly, in Java we would write:

String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
  .getExtension(MyProtoFile.myOption);

In Python it would be:

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

Custom options can be defined for every kind of construct in theProtocol Buffers language. Here is an example that uses every kind of 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";
  }
}

Note that if you want to use a custom option in a package otherthan the one in which it was defined, you must prefix the option name with thepackage name, just as you would for type names. For example:

// 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!";
}

One last thing: Since custom options are extensions, they mustbe assigned field numbers like any other field or extension. In the examplesabove, we have used field numbers in the range 50000-99999. This range isreserved for internal use within individual organizations, so you can usenumbers in this range freely for in-house applications. If you intend to usecustom options in public applications, however, then it is important that youmake sure that your field numbers are globally unique. To obtain globallyunique field numbers, please send a request to [email protected]. Simplyprovide your project name (e.g. Object-C plugin) and your project website (ifavailable). Usually you only need one extension number. You can declaremultiple options with only one extension number by putting them in asub-message:

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}
 
extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}
 
// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

Also, note that each option type (file-level, message-level,field-level, etc.) has its own number space, so e.g. you could declareextensions of FieldOptions and MessageOptions with the same number.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章