英文: Proto Buffers Language Guidephp
本指南描述了怎樣使用protocol buffer 語法來構造你的protocol buffer數據,包括.proto文件語法以及怎樣生成.proto文件的數據訪問類。
(本文只針對proto2的語法)html
本文是一個參考指南——若是要查看如何使用本文中描述的多個特性的按部就班的例子,請在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找須要的語言的教程。java
先來看一個很是簡單的例子。假設你想定義一個「搜索請求」的消息格式,每個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。能夠採用以下的方式來定義消息類型的.proto文件了:python
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)或其餘消息類型。api
正如上述文件格式,在消息定義中,每一個字段都有惟一的一個數字標識符。這些標識符是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不可以再改變。注:[1,15]以內的標識號在編碼的時候會佔用一個字節。[16,2047]以內的標識號則佔用2個字節。因此應該爲那些頻繁出現的消息元素保留 [1,15]以內的標識號。切記:要爲未來有可能添加的、頻繁出現的標識號預留一些標識號。安全
最小的標識號能夠從1開始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。若是非要在.proto文件中使用這些預留標識號,編譯時就會報警。服務器
所指定的消息字段修飾符必須是以下之一:框架
因爲一些歷史緣由,基本數值類型的repeated的字段並無被儘量地高效編碼。在新的代碼中,用戶應該使用特殊選項[packed=true]來保證更高效的編碼。如:ide
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.
}
|
當用protocolbuffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼能夠操做在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。
你能夠從以下的文檔連接中獲取每種語言更多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)。一個格式良好的消息能夠包含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例子是徹底等價的。
若是一個已有的消息格式已沒法知足新的需求——如,要在消息中添加一個額外的字段——可是同時舊版本寫的代碼仍然可用。不用擔憂!更新消息而不破壞已有代碼是很是簡單的。在更新時只要記住如下的規則便可。
經過擴展,能夠將一個範圍內的字段標識號聲明爲可被第三方擴展所用。而後,其餘人就能夠在他們本身的.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字段。 你可使用case()或者WhichOneof() 方法檢查哪一個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 特性:
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());
|
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
|
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 重用問題
固然能夠爲.proto文件新增一個可選的package聲明符,用來防止不一樣的消息類型有命名衝突。如:
1
2
|
package foo.bar;
message Open {
... }
|
在其餘的消息格式定義中可使用包名+消息名的方式來定義域的類型,如:
1
2
3
4
5
|
message Foo {
...
required foo.bar.Open open =
1;
...
}
|
包的聲明符會根據使用語言的不一樣影響生成的代碼。
Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每一個包會被看做是其父類包的內部類。固然對於 (foo.bar.Baz)這樣以「.」分隔的意味着是從最外圍開始的。ProtocolBuffer編譯器會解析.proto文件中定義的全部類型名。 對於不一樣語言的代碼生成器會知道如何來指向每一個具體的類型,即便它們使用了不一樣的規則。
若是想要將消息類型用在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.
在定義.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文件並且並不盲目追求速度的 應用中。
1
|
option optimize_
for = CODE_SIZE;
|
1
2
3
4
5
6
7
|
// This file relies on plugins to generate service code.
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
|
1
2
3
4
5
6
7
|
message Foo {
option message_set_wire_format = true;
extensions
4 to max;
}
|
1
|
repeated int32 samples = 4 [packed=true];
|
1
|
optional int32 old_field = 6 [deprecated=true];
|
ProtocolBuffers容許自定義並使用選項。該功能應該屬於一個高級特性,對於大部分人是用不到的。因爲options是定在 google/protobuf/descriptor.proto中的,所以你能夠在該文件中進行擴展,定義本身的選項。如:
1
2
3
4
5
6
7
8
9
|
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
|
在上述代碼中,經過對MessageOptions進行擴展定義了一個新的消息級別的選項。當使用該選項時,選項的名稱須要使用()包裹起來,以代表它是一個擴展。在C++代碼中能夠看出my_option是以以下方式被讀取的。
1
|
string value = MyMessage::descriptor()->options().GetExtension(my_option);
|
在Java代碼中的讀取方式以下:
1
2
|
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);
|
在Python中:
1
2
|
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]
|
正如上面的讀取方式,定製選項對於Python並不支持。定製選項在protocol buffer語言中可用於任何結構。下面就是一些具體的例子:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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";
}
}
|
注:若是要在該選項定義以外使用一個自定義的選項,必需要由包名 + 選項名來定義該選項。如:
1
2
3
4
5
6
|
// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
|
1
2
3
4
5
6
|
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
option (foo.my_option) = "Hello world!";
}
|
最後一件事情須要注意:由於自定義選項是可擴展的,它必須象其它的域或擴展同樣來定義標識號。正如上述示例,[50000-99999]已經被佔 用,該範圍內的值已經被內部所使用,固然了你能夠在內部應用中隨意使用。若是你想在一些公共應用中進行自定義選項,你必須確保它是全局惟一的。能夠經過protobuf-global-extension-registry@google.com來獲取全局惟一標識號。 只需提供你的項目名和項目網站. 一般你只須要一個擴展號。 你可使用一個擴展號聲明多個選項:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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" }];
}
|
能夠經過定義好的.proto文件來生成Java、Python、C++代碼,須要基於.proto文件運行protocol buffer編譯器protoc。運行的命令以下所示:
1
|
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
|
IMPORT_PATH聲明瞭一個.proto文件所在的具體目錄。若是忽略該值,則使用當前目錄。若是有多個目錄則能夠 對--proto_path 寫屢次,它們將會順序的被訪問並執行導入。-I=IMPORT_PATH是它的簡化形式。
固然也能夠提供一個或多個輸出路徑:
你必須提供一個或多個.proto文件做爲輸入。多個.proto文件可以一次所有聲明。雖然這些文件是相對於當前目錄來命名的,每一個文件必須在一個IMPORT_PATH中,只有如此編譯器才能夠決定它的標準名稱。
中文翻譯出處: http://www.open-open.com/home/space.php?uid=37924&do=blog&id=5873
原文: https://developers.google.com/protocol-buffers/docs/proto#generating
轉載時加入了新增長的內容
from: https://colobu.com/2015/01/07/Protobuf-language-guide/