SearchRequest.proto定義了每一個查詢請求的消息格式,每一個請求都會有查詢關鍵詞query,查詢結果的頁數,每頁的結果數量這三個屬性。因而
在這個例子中,SearchRequest的field都是基本類型,兩個integer(page_number和result_per_page)和一個Stirng(query),也能夠指定複雜的類型屬性,包括枚舉和其它類型。
注: 因爲required是永遠的,應該很是慎重地給message某個字段設置爲required。若是將來你但願中止寫入或者輸出某個required字段,那就會成爲問題;由於舊的reader將覺得沒有這個字段沒法初始化message,會丟掉這部分信息。一些來自google的工程師們指出使用required弊大於利,儘可能使用optional和repeated。
The protocol compiler searches for imported files in a set of directories specified on the protocol compiler command line using the -I/--proto_path flag.
If no flag was given, it looks in the directory in which the compiler was invoked.
In general you should set the --proto_path flag to the root of your project and use fully qualified names for all imports.
Extentions
extensions 聲明一個消息中的必定範圍的field的順序數字用於進行擴展。其它人能夠在本身的.proto文件中從新定義這些消息field,而不須要去修改原始的.proto文件
message Foo {
//
extensions 100 to 199;
}
這些說明100-199的field是保留的。其它用戶能夠用這些field在他們本身的.proto文件中添加新的fields給Foo。舉例:
extend Foo {
optional int32 bar = 126;
}
說明 Foo有一個optional的int32類型的名稱爲bar的field
當Foo的message編碼後,數據格式就跟用戶在Foo中定義一個新的field徹底同樣。可是你在程序中訪問extension field的方式與訪問正常的屬性略微有點不一樣。生成的extensions的訪問代碼是不一樣的。舉例:c++中如何set屬性bar的值:
Foo foo;
foo.SetExtension(bar,15);
一樣,Foo 定義了模板訪問器 HasExtendsion(),ClearExtension(),GetExtension(),MutableExtension(),AddExtension().
全部 訪問
注: extensions能使用任何field類型,包括自定義消息類型。
能聲明extensions在另外一個message中
message Baz {
extend Foo {
optional int32 bar = 126;
}
}
在這個例子中, the C++ 代碼訪問訪問這個屬性:
Foo foo;
foo.SetExtension(Baz::bar, 15);
換句話說,這麼作惟一影響是bar定義在了Baz的範圍以內。
注意:容易混淆的地方 聲明一個消息內部的繼承類並不意味着外部類和extended類有任何關係。特別 以上的例子並不意味着Baz是任何Foo的子類。這些只是意味着符號bar是聲明在Baz的範圍以內的,它看起來更像是個靜態成員。
一個通用的模式是在extensions的field範圍內來定義extensions,舉例說明,這裏有一個Foo的extension做爲Baz的一部分的屬性類型是Baz
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
}
沒有必要非得在message內部定義一個extension的類型。你也能夠這麼作:
message Baz {
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}
事實上,上面的這個語法更加有效地避免混淆。正如上文所述,內部的那種語法語法對於不是熟悉extensions的人來講,常常會錯認爲子類。
很是重要的一點是雙方不能使用一樣數字添加同樣的message類型,這樣extension會被解釋爲錯誤類型。
可能須要有一個關於field的數字順序的約定來保證你的project不會發生這樣的重複的問題。
若是你的field數字比較大的話,可使用max來指定你的textension範圍上升到最大的範圍
message Foo {
extensions 1000 to max;
}
max is 229 - 1, or 536,870,911.
19000-19999是protocol buffers的使用的字段,因此這個範圍內的數字須要區別開來。
Packages
能夠給一個.protol文件增長一個optional的package描述,來保證message儘可能不會出現名字相同的重名。
package foo.bar;
message Open {
}
也能夠在指定field類型的時候使用
message Foo {
required foo.bar.Open open = 1;
}
package會根據選擇的語言來生成不一樣的代碼:
C++ 生成的classes是用C++的namespace來區分的。舉例:Open would be in the namespace foo::bar。
Java package用於Java的package,除非你單獨的指定一個option java_package 在.proto文件中。
Python package是被忽略的,由於Python的modules是經過它們的文件位置來組織的。
在protocol buffer中package名稱的方案看起來像C++,首先,最裏面的範圍被搜索,而後搜索次一級的範圍,
每一個package被認爲在他的父package內。一個. (.foo.bar.Baz)意味着從最外層開始.
options
在一個proto文件中,還能夠存在一些options。Options不能改變一個聲明的總體的意義,可是能夠影響必定的上下文。
可用的options的完整list定義在 Google/protobuf/descriptor.proto
一些options是第一級的,意味着它們應該被寫在頂級範圍,而不是在任何message,enum,sercie的定義中。
一些options是message級別的,意味着它們應該被寫入message的描述中,
一些options是field-level級別的,意味着它們應該被寫入field的描述中,
options也能夠被寫入enum類型中,enum的值,service類型 和service方法;
列舉了經常使用的options:
java_package(file option)
定義生成的java class的package。若是在proto文件中沒有明確的java_package選項,那麼默認會使用package關鍵字指定的package名。
可是proto package一般不會好於Java packages,由於proto packages一般不會以domain名稱開始。
若是不生成java代碼,此選項沒有任何影響。
option java_package = "com.example.foo";
java_outer_classname:(file option)
指定想要生成的class名稱,若是此參數沒有指定的話,那麼默認使用.proto文件名來作爲類名,而且採用駝峯表示(好比:foo_bar.proto 爲 FooBar.java)
若是不生成java代碼,此選項沒有影響。
option java_outer_classname = "Ponycopter";
optimize_for (file option)
能夠設置爲speed、code_size或者lite_runtime.
SPEED:默認。protocol編譯器會生成classes代碼,提供了message類的序列化、轉換和其它通用操做。這個代碼是被高度優化過的。
CODE_SIZE: protocol編譯器會生成最小的classes,而且依賴共享、基於反射的代碼實現序列化、轉換和其它通用操做。生成的classes代碼小於speed,可是操做會慢一點。classes會實現跟SPEED模式同樣的公共API。這個模式一般用在一個應用程序包含了大量的proto文件,可是並不須要全部的代碼都執行得很快
LITE_RUNTIME: protocol編譯器會生成僅僅依賴 lite 運行庫(libprotobuf-lite代替libprotobuf)。lite運行時比全量庫小不少,省略了某種特性(如: descriptors and reflection)這個選項對於運行在像移動手機這種有約束平臺上的應用更有效。 編譯器仍然會對全部方法生成很是快的代碼實現,就像SPEED模式同樣。protocol編譯器會用各類語言來實現MessageList接口,可是這個接口僅僅提供了其它模式實現的Message接口的一部分方法子集。
例子
option optimize_for = CODE_SIZE;
cc_generic_services, java_generic_services, py_generic_services (file options)
不管如何,protoc編譯器會生成基於C++,Java,Python的抽象service代碼,這些默認都是true。截至到2.3.0版本,RPC實現提供了代碼生成插件去生成代碼,再也不使用抽象類。
// This file relies on plugins to generate service code.
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
message_set_wire_format (message option)
若是設置爲true,消息使用不一樣的二進制格式來兼容谷歌內部使用的稱爲MessageSet的舊格式。用戶在google之外使用,將再也不須要使用這個option。
消息必須按照如下聲明
message Foo {
option message_set_wire_format = true;
extensions 4 to max;
}
packed (field option)
若是設置爲true, 一個repeated的基本integer類型的field,會使用一種更加緊湊的壓縮編碼。請注意,在2.3.0版以前,protocol生成的解析邏輯收到未預期的壓縮的數據將會忽略掉。所以,改變一個已經存在的field,必定會破壞其線性兼容性。在2.3.0之後,這種改變就是安全的,解析邏輯能夠識別壓縮和不壓縮的格式,可是,必定要當心那些使用原先舊版本的protocol的程序。
repeated int32 samples = 4 [packed=true];
deprecated (field option):
若是設置爲true,表示這個field被廢棄,應該使用新代碼。大多數語言中,這個沒有任何影響。在java中,會生成@Deprecated的註釋。將來,其它語言代碼在field的訪問方法上也會生成相應的註釋。
optional int32 old_field = 6 [deprecated=true];
protocol buffer還容許你自定義options。這是個高級特性,大多數人並不須要。options其實都定義在 google/protobuf/descriptor.proto文件中。
自定義的options是簡單的,繼承這些messages
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
這裏咱們定義了一個message級別的消息選項,當使用這個options的時候,選項的名稱必須用括號括起來,以代表它是一個extension。
咱們在C++中讀取my_option的值就像下面這樣:
string value = MyMessage::descriptor()->options().GetExtension(my_option);
這裏,MyMessage::descriptor()->options()返回的MessageOptions protocol類型 message。
讀取自定義就如同讀取繼承屬性同樣。
在Java中
String value = MyProtoFile.MyMessage.getDescriptor().getOptions().getExtension(MyProtoFile.myOption);
自定義options能夠對任何message的組成元素進行定義
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";
}
}
若是想使用在package裏面的自定義的option,必需要option前使用包名,以下
// 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!";
}
最後一件事:既然自定義的options是extensions,他們必須指定field number就像其它field或者extension同樣。若是你要在公共應用中使用自定義的options,那麼必定要確認你的field numbers是全局惟一的
你能經過多選項帶有一個extension 把它們放入一個子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" }];
}
生成class代碼
爲了生成java、python、C++代碼,你須要運行protoc編譯器 protoc 編譯.proto文件。
編譯器運行命令:
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_path的縮寫
輸出目錄
--cpp_out 生成C++代碼在DST_DIR目錄
--java_out 生成Java代碼在DST_DIR目錄
--python_out 生成Python代碼在DST_DIR目錄
有個額外的好處,若是DST是.zip或者.jar結尾,那麼編譯器將會按照給定名字輸入到一個zip壓縮格式的文件中。
輸出到.jar會有一個jar指定的manifest文件。注意 若是輸出文件已經存在,它將會被覆蓋;編譯器的智能不足以自動添加文件到一個存在的壓縮文件中。
你必須提供一個或者多個.proto文件用做輸入。雖然文件命名關聯到當前路徑,每一個文件必須在import_path路徑中一邊編譯器能規定它的規範名稱
更新message
若是一個message 再也不知足全部須要,須要對字段進行調整.(舉例:對message增長一個額外的字段,可是仍然有支持舊格式message的代碼在運行)
要注意如下幾點:
一、不要修改已經存在字段的數字順序標示
二、能夠增長optional或者repeated的新字段。這麼作之後,全部經過舊格式message序列化的數據均可以經過新代碼來生成對應的對象,正如他們不會丟失任何required元素。
你應該爲這些元素添加合理的默認值,以便新代碼能夠與舊代碼生成的消息交互。 新代碼建立的消息中舊代碼不存在的字段,在解析的時候,舊代碼會忽略掉新增的字段。
不管如何,未知的field不會被丟棄,若是message晚點序列化,爲。
注意 未知field對於Python來講當前不可用。
三、非required字段均可以轉爲extension ,反之亦然,只要type和number保持不變。
四、int32, uint32, int64, uint64, and bool 是全兼容的。這意味着你能改變一個field從這些類型中的一個改變爲另外一個,而不用考慮會打破向前、向後兼容性。
若是一個數字是經過網絡傳輸而來的相應類型轉換,你將會遇到type在C++中遇到的問題(e.g. if a 64-bit number is read as an int32, it will be truncated to 32 bits)
五、sint32 and sint64 彼此兼容,可是不能兼容其它integer類型.
六、string and bytes 在UTF-8編碼下是兼容的.
七、若是bytes包含一個message的編碼,內嵌message與bytes兼容.
八、fixed32 兼容 sfixed32, fixed64 兼容 sfixed64.
九、optional 兼容 repeated. 用一個repeat字段的編碼結果做爲輸入,認爲這個字段是可選擇的客戶端會這樣處理,若是是原始類型的話,得到最後的輸入做爲相應的option值;若是是message 類型,合併全部輸入元素.
十、更改默認值一般是OK的.要記得默認值並不會經過網絡發送,若是一個程序接受一個特定字段沒有設置值的消息,應用將會使用本身的版本協議定義的默認值,不會看見發送者的默認值.