1. 程式人生 > >Protobuf 語法指南

Protobuf 語法指南

英文: Proto Buffers Language Guide

本指南描述了怎樣使用protocol buffer 語法來構造你的protocol buffer資料,包括.proto檔案語法以及怎樣生成.proto檔案的資料訪問類。
(本文只針對proto2的語法)

本文是一個參考指南——如果要檢視如何使用本文中描述的多個特性的循序漸進的例子,請在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查詢需要的語言的教程。

定義一個訊息型別

先來看一個非常簡單的例子。假設你想定義一個“搜尋請求”的訊息格式,每一個請求含有一個查詢字串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以採用如下的方式來定義訊息型別的.proto檔案了:

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

SearchRequest訊息格式有3個欄位,在訊息中承載的資料分別對應於每一個欄位。其中每個欄位都有一個名字和一種型別。

指定欄位型別

在上面的例子中,所有欄位都是標量型別:兩個整型(page_number和result_per_page),一個string型別(query)。當然,你也可以為欄位指定其他的合成型別,包括列舉(enumerations)或其他訊息型別。

分配標識號

正如上述檔案格式,在訊息定義中,每個欄位都有唯一的一個數字識別符號。這些識別符號是用來在訊息的二進位制格式中識別各個欄位的,一旦開始使用就不能夠再改變。注:[1,15]之內的標識號在編碼的時候會佔用一個位元組。[16,2047]之內的標識號則佔用2個位元組。所以應該為那些頻繁出現的訊息元素保留 [1,15]之內的標識號。切記:要為將來有可能新增的、頻繁出現的標識號預留一些標識號。

最小的標識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto檔案中使用這些預留標識號,編譯時就會報警。

指定欄位規則

所指定的訊息欄位修飾符必須是如下之一:

  • required:一個格式良好的訊息一定要含有1個這種欄位。表示該值是必須要設定的;
  • optional:訊息格式中該欄位可以有0個或1個值(不超過1個)。
  • repeated:在一個格式良好的訊息中,這種欄位可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java中的List。

由於一些歷史原因,基本數值型別的repeated的欄位並沒有被儘可能地高效編碼。在新的程式碼中,使用者應該使用特殊選項[packed=true]來保證更高效的編碼。如:

1 repeated int32 samples = 4 [packed=true];

required是永久性的:在將一個欄位標識為required的時候,應該特別小心。如果在某些情況下不想寫入或者傳送一個required的欄位,將原始該欄位修飾符更改為optional可能會遇到問題——舊版本的使用者會認為不含該欄位的訊息是不完整的,從而可能會無目的的拒絕解析。在這種情況下,你應該考慮編寫特別針對於應用程式的、自定義的訊息校驗函式。Google的一些工程師得出了一個結論:使用required弊多於利;他們更 願意使用optional和repeated而不是required。當然,這個觀點並不具有普遍性。

新增更多訊息型別

在一個.proto檔案中可以定義多個訊息型別。在定義多個相關的訊息的時候,這一點特別有用——例如,如果想定義與SearchResponse訊息型別對應的回覆訊息格式的話,你可以將它新增到相同的.proto檔案中,如:

1 2 3 4 5 6 7 8 9 message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; }   message SearchResponse { ... }

添加註釋

向.proto檔案添加註釋,可以使用C/C++/java風格的雙斜槓(//) 語法格式,如:

1 2 3 4 5 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. }

從.proto檔案生成了什麼?

當用protocolbuffer編譯器來執行.proto檔案時,編譯器將生成所選擇語言的程式碼,這些程式碼可以操作在.proto檔案中定義的訊息型別,包括獲取、設定欄位值,將訊息序列化到一個輸出流中,以及從一個輸入流中解析訊息。

  • 對C++來說,編譯器會為每個.proto檔案生成一個.h檔案和一個.cc檔案,.proto檔案中的每一個訊息有一個對應的類。
  • 對Java來說,編譯器為每一個訊息型別生成了一個.java檔案,以及一個特殊的Builder類(該類是用來建立訊息類介面的)。
  • 對Python來說,有點不太一樣——Python編譯器為.proto檔案中的每個訊息型別生成一個含有靜態描述符的模組,,該模組與一個元類(metaclass)在執行時(runtime)被用來建立所需的Python資料訪問類。

你可以從如下的文件連結中獲取每種語言更多API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

標量數值型別

一個標量訊息欄位可以含有一個如下的型別——該表格展示了定義於.proto檔案中的型別,以及與之對應的、在自動生成的訪問類中定義的型別:

.proto型別

Java 型別

C++型別

備註

double

double

double

 

float

float

float

 

int32

int

int32

使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint32。

int64

long

int64

使用可變長編碼方式。編碼負數時不夠高效——如果你的欄位可能含有負數,那麼請使用sint64。

uint32

int[1]

uint32

Uses variable-length encoding.

uint64

long[1] uint64 Uses variable-length encoding.

sint32

int

int32

使用可變長編碼方式。有符號的整型值。編碼時比通常的int32高效。

sint64

long

int64

使用可變長編碼方式。有符號的整型值。編碼時比通常的int64高效。

fixed32

int[1]

uint32

總是4個位元組。如果數值總是比總是比228大的話,這個型別會比uint32高效。

fixed64

long[1]

uint64

總是8個位元組。如果數值總是比總是比256大的話,這個型別會比uint64高效。

sfixed32

int

int32

總是4個位元組。

sfixed64

long

int64

總是8個位元組。

bool

boolean

bool

 

string

String

string

一個字串必須是UTF-8編碼或者7-bit ASCII編碼的文字。

bytes

ByteString

string

可能包含任意順序的位元組資料。

你可以在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到更多“序列化訊息時各種型別如何編碼”的資訊。

Optional的欄位和預設值

如上所述,訊息描述中的一個元素可以被標記為“可選的”(optional)。一個格式良好的訊息可以包含0個或一個optional的元素。當解 析訊息時,如果它不包含optional的元素值,那麼解析出來的物件中的對應欄位就被置為預設值。預設值可以在訊息描述檔案中指定。例如,要為 SearchRequest訊息的result_per_page欄位指定預設值10,在定義訊息格式時如下所示:

1 optional int32 result_per_page = 3 [default = 10];

如果沒有為optional的元素指定預設值,就會使用與特定型別相關的預設值:對string來說,預設值是空字串。對bool來說,預設值是false。對數值型別來說,預設值是0。對列舉來說,預設值是列舉型別定義中的第一個值。

列舉

當需要定義一個訊息型別的時候,可能想為一個欄位指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest訊息新增一個 corpus欄位,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現這一點:通過向訊息定義中新增一個列舉(enum)就可以了。一個enum型別的欄位只能用指定的常量集中的一個值作為其值(如果嘗 試指定不同的值,解析器就會把它當作一個未知的欄位來對待)。在下面的例子中,在訊息格式中添加了一個叫做Corpus的列舉型別——它含有所有可能的值 ——以及一個型別為Corpus的欄位:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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, 否則 protocol編譯器會產生錯誤資訊。

1 2 3 4 5 6 7 8 9 10 11 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)。

關於如何在你的應用程式的訊息中使用列舉的更多資訊,請檢視所選擇的語言http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。

使用其他訊息型別

你可以將其他訊息型別用作欄位型別。例如,假設在每一個SearchResponse訊息中包含Result訊息,此時可以在相同的.proto檔案中定義一個Result訊息型別,然後在SearchResponse訊息中指定一個Result型別的欄位,如:

1 2 3 4 5 6 7 8 9 message SearchResponse { repeated Result result = 1; }   message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; }

匯入定義

在上面的例子中,Result訊息型別與SearchResponse是定義在同一檔案中的。如果想要使用的訊息型別已經在其他.proto檔案中已經定義過了呢?
你可以通過匯入(importing)其他.proto檔案中的定義來使用它們。要匯入其他.proto檔案的定義,你需要在你的檔案中新增一個匯入宣告,如:

1 import "myproject/other_protos.proto";

預設情況下你只能使用直接匯入的.proto檔案中的定義. 然而, 有時候你需要移動一個.proto檔案到一個新的位置, 可以不直接移動.proto檔案, 只需放入一個dummy .proto 檔案在老的位置, 然後使用import轉向新的位置:

1 2 // new.proto // All definitions are moved here
1 2 3 4 // old.proto // This is the proto that all clients are importing. import public "new.proto"; import "other.proto";

// client.proto

1 2 import "old.proto"; // You use definitions from old.proto and new.proto, but not other.proto

protocol編譯器就會在一系列目錄中查詢需要被匯入的檔案,這些目錄通過protocol編譯器的命令列引數-I/–import_path指定。如果不提供引數,編譯器就在其呼叫目錄下查詢。

巢狀型別

你可以在其他訊息型別中定義、使用訊息型別,在下面的例子中,Result訊息就定義在SearchResponse訊息內,如:

1 2 3 4 5 6 7 8 message SearchResponse { message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; }

如果你想在它的父訊息型別的外部重用這個訊息型別,你需要以Parent.Type的形式使用它,如:

1 2 3 message SomeOtherMessage { optional SearchResponse.Result result = 1; }

當然,你也可以將訊息巢狀任意多層,如:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 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的另一種方法是 :

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

一個“組”只是簡單地將一個巢狀訊息型別和一個欄位捆綁到一個單獨的宣告中。在程式碼中,可以把它看成是含有一個Result型別、名叫result的欄位的訊息(後面的名字被轉換成了小寫,所以它不會與前面的衝突)。

因此,除了資料傳輸格式不同之外,這個例子與上面的SearchResponse例子是完全等價的。

更新一個訊息型別

如果一個已有的訊息格式已無法滿足新的需求——如,要在訊息中新增一個額外的欄位——但是同時舊版本寫的程式碼仍然可用。不用擔心!更新訊息而不破壞已有程式碼是非常簡單的。在更新時只要記住以下的規則即可。

  • 不要更改任何已有的欄位的數值標識。
    *所新增的任何欄位都必須是optional或repeated的。這就意味著任何使用“舊”的訊息格式的程式碼序列化的訊息可以被新的程式碼所解析,因為它們 不會丟掉任何required的元素。應該為這些元素設定合理的預設值,這樣新的程式碼就能夠正確地與老程式碼生成的訊息互動了。類似地,新的程式碼建立的訊息 也能被老的程式碼解析:老的二進位制程式在解析的時候只是簡單地將新欄位忽略。然而,未知的欄位是沒有被拋棄的。此後,如果訊息被序列化,未知的欄位會隨之一 起被序列化——所以,如果訊息傳到了新程式碼那裡,則新的欄位仍然可用。注意:對Python來說,對未知欄位的保留策略是無效的。
  • 非required的欄位可以移除——只要它們的標識號在新的訊息型別中不再使用(更好的做法可能是重新命名那個欄位,例如在欄位前新增“OBSOLETE_”字首,那樣的話,使用的.proto檔案的使用者將來就不會無意中重新使用了那些不該使用的標識號)。
  • 一個非required的欄位可以轉換為一個擴充套件,反之亦然——只要它的型別和標識號保持不變。
  • int32, uint32, int64, uint64,和bool是全部相容的,這意味著可以將這些型別中的一個轉換為另外一個,而不會破壞向前、 向後的相容性。如果解析出來的數字與對應的型別不相符,那麼結果就像在C++中對它進行了強制型別轉換一樣(例如,如果把一個64位數字當作int32來 讀取,那麼它就會被截斷為32位的數字)。
  • sint32和sint64是互相相容的,但是它們與其他整數型別不相容。
  • string和bytes是相容的——只要bytes是有效的UTF-8編碼。
  • 巢狀訊息與bytes是相容的——只要bytes包含該訊息的一個編碼過的版本。
  • fixed32與sfixed32是相容的,fixed64與sfixed64是相容的。

擴充套件

通過擴充套件,可以將一個範圍內的欄位標識號宣告為可被第三方擴充套件所用。然後,其他人就可以在他們自己的.proto檔案中為該訊息型別宣告新的欄位,而不必去編輯原始檔案了。看個具體例子:

1 2 3 4 message Foo { // ... extensions 100 to 199; }

這個例子表明:在訊息Foo中,範圍[100,199]之內的欄位標識號被保留為擴充套件用。現在,其他人就可以在他們自己的.proto檔案中新增新欄位到Foo裡了,但是新增的欄位標識號要在指定的範圍內——例如:

1 2 3 extend Foo { optional int32 bar = 126; }

這個例子表明:訊息Foo現在有一個名為bar的optional int32欄位。

當用戶的Foo訊息被編碼的時候,資料的傳輸格式與使用者在Foo裡定義新欄位的效果是完全一樣的。
然而,要在程式程式碼中訪問擴充套件欄位的方法與訪問普通的欄位稍有不同——生成的資料訪問程式碼為擴充套件準備了特殊的訪問函式來訪問它。例如,下面是如何在C++中設定bar的值:

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

類似地,Foo類也定義了模板函式 HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()。這些函式的語義都與對應的普通欄位的訪問函式相符。要檢視更多使用擴充套件的資訊,請參考相應語言的程式碼生成指南。注:擴充套件可 以是任何欄位型別,包括訊息型別。

巢狀的擴充套件

可以在另一個型別的範圍內宣告擴充套件,如:

1 2 3 4 5 6 message Baz { extend Foo { optional int32 bar = 126; } ... }

在此例中,訪問此擴充套件的C++程式碼如下:

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

In other words, the only effect is that bar is defined within the scope of Baz.

This is a common source of confusion: Declaring an extend block nested inside a message type does not imply any relationship between the outer type and the extended type. In particular, the above example does not mean that Baz is any sort of subclass of Foo. All it means is that the symbol bar is declared inside the scope of Baz; it's simply a static member.

一個通常的設計模式就是:在擴充套件的欄位型別的範圍內定義該擴充套件——例如,下面是一個Foo的擴充套件(該擴充套件是Baz型別的),其中,擴充套件被定義為了Baz的一部分:

1 2 3 4 5 6 message Baz { extend Foo { optional Baz foo_ext = 127; } ... }

然而,並沒有強制要求一個訊息型別的擴充套件一定要定義在那個訊息中。也可以這樣做:

1 2 3 4 5 6 7 8 message Baz { ... }   // This can even be in a different file. extend Foo { optional Baz foo_baz_ext = 127; }

事實上,這種語法格式更能防止引起混淆。正如上面所提到的,巢狀的語法通常被錯誤地認為有子類化的關係——尤其是對那些還不熟悉擴充套件的使用者來說。

選擇可擴充套件的標量符號

在同一個訊息型別中一定要確保兩個使用者不會擴充套件新增相同的標識號,否則可能會導致資料的不一致。可以通過為新專案定義一個可擴充套件標識號規則來防止該情況的發生。

如果標識號需要很大的數量時,可以將該可擴充套件標符號的範圍擴大至max,其中max是229 - 1, 或536,870,911。如下所示:

1 2 3 4 5 message Foo {   extensions 1000 to max;   }

max 是 2^29 - 1, 或者 536,870,911.

通常情況下在選擇標符號時,標識號產生的規則中應該避開[19000-19999]之間的數字,因為這些已經被Protocol Buffers實現中預留了。

Oneof

如果你的訊息中有很多可選欄位, 並且同時至多一個欄位會被設定, 你可以加強這個行為,使用oneof特性節省記憶體.

Oneof欄位就像可選欄位, 除了它們會共享記憶體, 至多一個欄位會被設定。 設定其中一個欄位會清除其它oneof欄位。 你可以使用case()或者WhichOneof() 方法檢查哪個oneof欄位被設定, 看你使用什麼語言了.

使用Oneof

為了在.proto定義Oneof欄位, 你需要在名字前面加上oneof關鍵字, 比如下面例子的test_oneof:

1 2 3 4 5 6 message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }

然後你可以增加oneof欄位到 oneof 定義中. 你可以增加任意型別的欄位, 但是不能使用 required, optional, repeated 關鍵字.

在產生的程式碼中, oneof欄位擁有同樣的 getters 和setters, 就像正常的可選欄位一樣. 也有一個特殊的方法來檢查到底那個欄位被設定. 你可以在相應的語言API中找到oneof API介紹.

Oneof 特性:

  • 設定oneof會自動清楚其它oneof欄位的值. 所以設定多次後,只有最後一次設定的欄位有值.
1 2 3 4 5 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 on the wire, only the last member seen is used in the parsed message.
  • oneof不支援擴充套件.
  • oneof不能 repeated.
  • 反射API對oneof 欄位有效.
  • 如果使用C++,需確保程式碼不會導致記憶體洩漏. 下面的程式碼會崩潰, 因為sub_message 已經通過set_name()刪除了.
1 2 3 4 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 the other’s oneof case: in the example below, msg1 will have a sub_message and msg2 will have a name.
1 2 3 4 5 6 7 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欄位沒有被賦值或者在一個不同的版本中賦值了。 你不會知道是哪種情況。

Tag 重用問題

  • Move optional fields into or out of a oneof: You may lose some of your information (some fields will be cleared) after the message is serialized and parsed.
  • Delete a oneof field and add it back: This may clear your currently set oneof field after the message is serialized and parsed.
  • Split or merge oneof: This has similar issues to moving regular optional fields.

包(Package)

當然可以為.proto檔案新增一個可選的package宣告符,用來防止不同的訊息型別有命名衝突。如:

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

在其他的訊息格式定義中可以使用包名+訊息名的方式來定義域的型別,如:

1 2 3 4 5 message Foo { ... required foo.bar.Open open = 1; ... }

包的宣告符會根據使用語言的不同影響生成的程式碼。

  • 對於C++,產生的類會被包裝在C++的名稱空間中,如上例中的Open會被封裝在 foo::bar空間中;
  • 對於Java,包宣告符會變為java的一個包,除非在.proto檔案中提供了一個明確有java_package;
  • 對於 Python,這個包宣告符是被忽略的,因為Python模組是按照其在檔案系統中的位置進行組織的。

包及名稱的解析

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

定義服務(Service)

如果想要將訊息型別用在RPC(遠端方法呼叫)系統中,可以在.proto檔案中定義一個RPC服務介面,protocol buffer編譯器將會根據所選擇的不同語言生成服務介面程式碼及存根。如,想要定義一個RPC服務並具有一個方法,該方法能夠接收 SearchRequest並返回一個SearchResponse,此時可以在.proto檔案中進行如下定義:

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

protocol編譯器將產生一個抽象介面SearchService以及一個相應的存根實現。存根將所有的呼叫指向RpcChannel,它是一 個抽象介面,必須在RPC系統中對該介面進行實現。如,可以實現RpcChannel以完成序列化訊息並通過HTTP方式來發送到一個伺服器。換句話說, 產生的存根提供了一個型別安全的介面用來完成基於protocolbuffer的RPC呼叫,而不是將你限定在一個特定的RPC的實現中。C++中的程式碼 如下所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using google::protobuf;   protobuf::RpcChannel* channel; protobuf::RpcController* controller; SearchService* service; SearchRequest request; SearchResponse response;   void DoSearch() { // You provide classes MyRpcChannel and MyRpcController, which implement // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController. channel = new MyRpcChannel("somehost.example.com:1234"); controller = new MyRpcController;   // The protocol compiler generates the SearchService class based on the // definition given above. service = new SearchService::Stub(channel);   // Set up the request. request.set_query( "protocol buffers");   // Execute the RPC. service->Search(controller, request, response, protobuf::NewCallback(&Done)); }   void Done() { delete service; delete channel; delete controller; }

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using google::protobuf;   class ExampleSearchService : public SearchService { public: void Search(protobuf::RpcController* controller, const SearchRequest* request, SearchResponse* response, protobuf::Closure* done) { if (request->query() == "google") { response->add_result()->set_url( "http://www.google.com"); } else if (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 = new ExampleSearchService; server.ExportOnPort( 1234, service); server.Run();   delete service; return 0; }

There are a number of ongoing third-party projects to develop RPC implementations for Protocol Buffers. For a list of links to projects we know about, see the third-party add-ons wiki page.

選項(Options)

在定義.proto檔案時能夠標註一系列的options。Options並不改變整個檔案宣告的含義,但卻能夠影響特定環境下處理方式。完整的可用選項可以在google/protobuf/descriptor.proto找到。

一些選項是檔案級別的,意味著它可以作用於最外範圍,不包含在任何訊息內部、enum或服務定義中。一些選項是訊息級別的,意味著它可以用在訊息定 義的內部。當然有些選項可以作用在域、enum型別、enum值、服務型別及服務方法中。到目前為止,並沒有一種有效的選項能作用於所有的型別。

如下就是一些常用的選擇:

  • java_package (file option): 這個選項表明生成java類所在的包。如果在.proto檔案中沒有明確的宣告java_package,就採用預設的包名。當然了,預設方式產生的 java包名並不是最好的方式,按照應用名稱倒序方式進行排序的。如果不需要產生java程式碼,則該選項將不起任何作用。如:
1 option java_package = "com.example.foo";
  • java_outer_classname (file option): 該選項表明想要生成Java類的名稱。如果在.proto檔案中沒有明確的java_outer_classname定義,生成的class名稱將會根據.proto檔案的名稱採用駝峰式的命名方式進行生成。如(foo_bar.proto生成的java類名為FooBar.java),如果不生成java程式碼,則該選項不起任何作用。如:
1 option java_outer_classname = "Ponycopter";
  • optimize_for (fileoption): 可以被設定為 SPEED, CODE_SIZE,or LITE_RUNTIME。這些值將通過如下的方式影響C++及java程式碼的生成:
    • SPEED (default): protocol buffer編譯器將通過在訊息型別上執行序列化、語法分析及其他通用的操作。這種程式碼是最優的。
    • CODE_SIZE: protocol buffer編譯器將會產生最少量的類,通過共享或基於反射的程式碼來實現序列化、語法分析及各種其它操作。採用該方式產生的程式碼將比SPEED要少得多, 但是操作要相對慢些。當然實現的類及其對外的API與SPEED模式都是一樣的。這種方式經常用在一些包含大量的.proto檔案而且並不盲目追求速度的 應用中。
    • LITE_RUNTIME: protocol buffer編譯器依賴於執行時核心類庫來生成程式碼(即採用libprotobuf-lite 替代libprotobuf)。這種核心類庫由於忽略了一 些描述符及反射,要比全類庫小得多。這種模式經常在移動手機平臺應用多一些。編譯器採用該模式產生的方法實現與SPEED模式不相上下,產生的類通過實現 MessageLite介面,但它僅僅是Messager介面的一個子集。