google protocol buffer -2-.proto 定義規則

essage爲主要關鍵字,相似於java中的class。
定義簡單message類型
SearchRequest.proto定義了每一個查詢請求的消息格式,每一個請求都會有查詢關鍵詞query,查詢結果的頁數,每頁的結果數量這三個屬性。因而
 
message SearchRequest{
        required string query = 1;
        optional int32 page_number = 2;
        optional int32 result_per_page =3;
        repeated int32 samples = 4 [packed=true];    
}
message定義了三個field,每一個field由名字和類型來組成。
  • 指定field類型
在這個例子中,SearchRequest的field都是基本類型,兩個integer(page_number和result_per_page)和一個Stirng(query),也能夠指定複雜的類型屬性,包括枚舉和其它類型。
  • 分配標籤
每一個field都是惟一數字的標記,這是用來標記這個field在message二進制格式中的位置的,一旦使用就不能再修改順序了。
注:標記從1-15只有一個字節編碼,包括自增加屬性(更多的見Protocol Buffer Encoding)
標記從16-2047佔用兩個字節。所以儘可能頻繁使用1-15,記住爲將來的擴展留下一些位置。
最小的tag你能夠定義爲1,最大2的29次方-1  536870922.你一樣不能使用19000-19999(這個位置已經被GPB本身實現),
  • 指定field規則
 
message SearchRequest{
        required string query = 1;
        optional int32 page_number = 2;
        optional int32 result_per_page =3;
        repeated int32 samples = 4 [packed=true];    
}
因爲歷史緣由,repeated字段若是是基本數字類型的話,不能有效地編碼。如今代碼可使用特殊選項[packed=true]來獲得更有效率的編碼。
注: 因爲required是永遠的,應該很是慎重地給message某個字段設置爲required。若是將來你但願中止寫入或者輸出某個required字段,那就會成爲問題;由於舊的reader將覺得沒有這個字段沒法初始化message,會丟掉這部分信息。一些來自google的工程師們指出使用required弊大於利,儘可能使用optional和repeated。
這個觀點並非通用的。
 
  • 更多message類型
多個message類型能被定義在一個簡單的.proto文件中,一般是建立具備關聯關係的message時候這麼做。
 
message SearchRequest{
        required string query = 1;
        optional int32 page_number = 2;
        optional int32 result_per_page =3;
        repeated int32 samples = 4 [packed=true];    
}
 
  • 添加註釋
使用c/C++ style
 
 
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?
        repeated int32 samples = 4 [packed=true];    
}
 
  • .proto文件自動生成代碼
protocol buffer編譯一個proto文件,生成對應語言的代碼。
大概包括各個字段的get和set方法,序列化message到輸出流的方法,從輸入流轉成message的方法。
C++,爲每一個proto生成一個.h和.cc文件
Java,爲每一個proto生成一個.java文件
Python,有點不一樣,生成一個module
 
  • 基本屬性  
  • optional字段和默認值
當含有optional字段的message從流轉換成對象的時候,若是沒有包含optional字段的數據,那麼對象的optional字段會設置成默認值。
默認值能夠做爲message的描述出現。舉個例子:
optional int32 result_per_page = 3 [default = 10];
如 
pasting
 果沒有指定默認值的話,string 默認爲空串,bool 默認爲false,數字類型默認0,枚舉類型,默認爲類型定義中的第一個值,
 
  • Enumerations
若是字段的屬性值是固定的幾個值,可使用枚舉
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 = WEB];
}
 
  • 自定義消息類型
可使用message類型作字段的屬性,看例子:
message SearchResponse {
  repeated Result result = 1;
}
message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}
 
 
  • import 定義
上面的例子SearchResponse 與Result在一個.proto文件中。其實也可使用另外一個.proto文件來定義字段類型。
你能夠經過import來定義。
import "myproject/other_protos.proto";
 
protocol編譯器查找引入文件是經過編譯器的命令參數 -I/--proto_path
若是沒有指定,就在protoc執行目錄下尋找。
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.
 
  • 內部類
你能夠定義和使用內部message類。
message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}
 
若是要引用內部類,則經過parent.type方式來調用
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;
    }
  }
}
 
  • Groups 
 廢棄的屬性,瞭解便可,採用內部類代替。
message SearchResponse {
  repeated group Result = 1 {
    required string url = 2;
    optional string title = 3;
    repeated string snippets = 4;
  }
}



 
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
能聲明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的人來講,常常會錯認爲子類。
 
  • 選擇Extension 順序數字
很是重要的一點是雙方不能使用一樣數字添加同樣的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是經過它們的文件位置來組織的。
 
  • Packages和name 
在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];
 
  • 自定義options
 
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的.要記得默認值並不會經過網絡發送,若是一個程序接受一個特定字段沒有設置值的消息,應用將會使用本身的版本協議定義的默認值,不會看見發送者的默認值.             
 
 
 
引用地址:http://www.blogjava.net/livery/articles/392084.html
相關文章
相關標籤/搜索