Protocol Buffers 語法指南

前兩篇文章,咱們歸納介紹《Google Protocol Buffers 概述》以及帶領你們簡單的《Google Protocol Buffers 入門》,接下來,再稍微詳細一點介紹Protocol Buffers書寫語言。該篇文章主要講解如何使用PB語言構建數據,包括.proto文件語法及若是使用.proto文件生成數據存取類。java

本篇主要包括:數組

  • 定義一個PB message類型
  • 介紹PB 數據類型
  • Optional字段及其默認值
  • 枚舉類型
  • 使用其餘Message類型做爲filed類型
  • 嵌套類型
  • 更新Message

2. 定義一個PB message類型

假如如今須要定義搜索請求的message格式,每條message包含三個字段:搜索語句(query string),須要的返回結果頁數(page_number),以及該頁上的結果數。可以下定義.proto文件。ide

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

該message定義聲明三個字段(name/value pairs),每一個字段有一個名字和類型。性能

 2.1 聲明字段類型

上例中,全部的字段類型均爲標準類型:兩個整型和一個字符串類型。固然,也能夠指定複合類型:枚舉類型和其餘自定義message類型。ui

2.2 給字段賦值數字標籤

從上例中能夠發現,message中定義的每一個字段都有一個惟一的數字標籤。該標籤的做用是在二進制message中惟一標示該字段,一旦定義該字 段的值就不可以再更改。有一點須要強調:1~15的數字標籤編碼後僅佔一個字節(byte),包括數字標籤和字段類型。16~2047的數字標籤佔兩個字 節(byte)。所以,1~15的數字標籤應該用於最頻繁出現的元素。設計時要考慮到不要一次用完1~15的標籤,要考慮到未來也可能出現頻繁出現的元 素。this

最小的數字標籤是1,最大的數字標籤是2的29次方-1,也即 536,870,911。可是並非這之間全部的數字標籤你都能用,例如 19000~19999。這個區間的數字標籤就像是java中的保留字同樣,他們是PB的保留數字標籤。若是該區間的數字標籤出如今.proto文件 中,PB編譯器會出錯。google

2.3 字段標示符

字段標示符有三個:編碼

message的沒一個字段,都要用以下的三個修飾符(modifier)來聲明:url

  1. required:必須賦值,不能爲空,不然該條message會被認爲是「uninitialized」。build一個 「uninitialized」 message會拋出一個RuntimeException異常,解析一條「uninitialized」 message會拋出一條IOException異常。除此以外,「required」字段跟「optional」字段並沒有差異。
  2. optional:字段能夠賦值,也能夠不賦值。假如沒有賦值的話,會被賦上默認值。對於簡單類型,默認值能夠本身設定,例如上例的 PhoneNumber中的PhoneType字段。若是沒有自行設定,會被賦上一個系統默認值,數字類型會被賦爲0,String類型會被賦爲空字符 串,bool類型會被賦爲false。對於內置的message,默認值爲該message的默認實例或者原型,即其內全部字段均爲設置。當獲取沒有顯式 設置值的optional字段的值時,就會返回該字段的默認值。
  3. repeated:該字段能夠重複任意次數,包括0次。重複數據的順序將會保存在protocol buffer中,將這個字段想象成一個能夠自動設置size的數組就能夠了。

因爲一些歷史緣由,數字類型的repeated字段性能有些不盡人意,可是,PB已經作了改進,可是須要再添加一點改動,即在聲明後添加[packed=true]例如:spa

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

Notice:應該格外當心定義Required字段。當由於某緣由要把Required字段改成 Optional字段是,會有問題,老版本讀取器會認爲消息中沒有該字段不完整,可能會拒絕或者丟棄該字段(Google文檔是這麼說的,可是我試了一 下,將required的改成optional的,再用原來required時候的解析代碼去讀,若是字段賦值的話,並不會出錯,可是若是字段未賦值,會 報這樣錯誤:Exception in thread 「main」 com.google.protobuf.InvalidProtocolBufferException: Message missing required fields:fieldname)。在設計時,儘可能將這種驗證放在應用程序端的完成。Google的一些工程師對此也很困惑,他們覺 得,required類型壞處大於好處,應該儘可能僅適用optional或者repeated的。但也並非全部的人都這麼想。

2.4 同一.proto文件定義多個message

PB支持同一.proto文件定義多個message。這在須要定義相關message的時候很是有用,例如:除了搜索請求message,還須要定義搜索響應message,能夠再同一.proto文件中定義:

?
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 ;
 
}
 
message SearchResponse {
 
...
 
}

2.5 添加評論

使用C/C++風格的註釋 // syntax,以下例子:

?
1
2
3
4
5
6
7
8
9
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.
 
}

2.6 編譯.proto文件後產生了什麼?

用PB 編譯器運行.proto文件後,會按照定義的格式,生成指定語言的一系列代買,這些代碼的功能包括:字段值的getter,setter,序列化message並寫入到輸出流,從輸入流接寫成message等。

對於Java,編譯器生成一個.java文件,該java文件內包含幾個內部類,分別對應.proto文件中定義的message 類型,以及未來用於建立message類實例的Builder類。

3. 標準值類型

.proto Type Notes C++ Type Java Type Python Type[2]
double   double double float
float   float float float
int32 使用可變長編碼. 對於負數比較低效,若是負數較多,請使用sint32 int32 int int
int64 使用可變長編碼. 對於負數比較低效,若是負數較多,請使用sint64 int64 long int/long
uint32 使用可變長編碼 uint32 int
int/long
uint64 使用可變長編碼 uint64 long
int/long
sint32 使用可變長編碼. Signed int value. 編碼負數比int32更高效 int32 int int
sint64 使用可變長編碼. Signed int value. 編碼負數比int64更高效 int64 long int/long
fixed32 恆定四個字節。若是數值幾乎老是大於2的28次方,該類型比unit32更高效。 uint32 int
int
fixed64 恆定四個字節。若是數值幾乎老是大於2的56次方,該類型比unit64更高效。 uint64 long
int/long
sfixed32 恆定四個字節 int32 int int
sfixed64 恆定八個字節 int64 long int/long
bool   bool boolean boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String str/unicode
bytes 包含任意數量順序的字節 string ByteString str

4. Optional字段及其默認值

上面提到,PB容許設置可選字段(optional)。顧名思義,在一條message中,該字段可設值也可不設。假如沒有設置,那麼在解析該字段的時候,會根據該字段類型,給其賦一個類型默認值。除此以外,也能夠在定義message格式的時候,就爲optional字段設置一個默認值,以下:

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

假如沒有賦值的話,會被賦上默認值。對於簡單類型,默認值能夠本身設定,例如上例的PhoneNumber中的PhoneType字段。若是沒有自行設定,會被賦上一個系統默認值,數字類型會被賦爲0,String類型會被賦爲空字符串,bool類型會被賦爲false。對於枚舉類型,默認值是枚舉列表中第一個值。

5. 枚舉類型

在定義message類型的時候,也許會有這樣一種需求:其中的一個字段僅須要包含預約義的若干個值便可。好比,對於每個搜索請求,現須要增長一個分類字段,分類包含:UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS or VIDEO。要實現該功能,僅須要增長一個枚舉類型字段。以下:

?
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爲true,不然PB編譯器會報錯。

?
1
2
3
4
5
6
7
8
9
10
11
12
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.
}

能夠定義枚舉在一個message內部,如上例。也能夠定義在message的外部,這樣的枚舉能夠被其餘任何.proto文件內的message複用。

6. 使用其餘Message類型做爲filed類型

PB容許使用message類型做爲filed類型。例如,在搜索相應message中,包含一個結果message。此時,只須要定義一個結果 message,而後再.proto文件中,在搜索結果message中新增一個字段,該字段的類型設置爲結果message便可。以下:

?
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;
}

6.1 導入定義

在上例中,Result message類型與SearchResponse 定義在同一個文件中,假若有這麼一種狀況,這裏所要使用的Resultmessage已經在其餘的.proto文件中定義了呢?

能夠經過導入其餘.proto文件來使用其內的定義。爲達此目的,須要在現.proto文件前增長一條import語句:

?
1
import "myproject/other_protos.proto";

7. 嵌套類型

PB支持message內嵌套message,以下例子中,Result message 定義在了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;
}

若是想要在父Message外複用該message的話,能夠用Parent.Type格式來引用。

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

PB支持無限深層次的message嵌套:

?
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;
     }
   }
}

8. 更新Message類型

若是現有message類型不能在知足業務需求,例如,須要新增一個字段,可是咱們卻但願依然可以使用原來的.proto生成的代碼。徹底沒有問題,僅需記住以下規則:

  1. 千萬不要修改現有字段後邊的數值標籤
  2. 只能新增optional或者repeated字段
  3. 能夠刪除非必須字段,可是他們的數字標籤不能再被使用。最好的方法是不刪除,而是修更名字,好比在前綴上加OBSOLETE_,這樣就能夠避免後人儘可能少的出錯。
  4. 非required字段能夠轉化成extension字段,反之亦然,同時保留原類型和數字標籤
  5. int32, uint32, int64, uint64, 和bool是兼容的。即這些字段能夠相互切換,在代碼處理的時候,不會出錯,可是當心範圍小的數據接收範圍大的數據會發生截斷
  6. sint32, sint64是相互兼容的,可是不與其餘整型類型兼容
  7. string和bytes是兼容的,由於bytes也是合法的UTF-8
  8. Embedded messages are compatible with bytes if the bytes contain an encoded version of the message(不知道怎麼翻譯了)
  9. fixed32與 sfixed32兼容, fixed64 與sfixed64兼容
  10. optional與repeated兼容,也存在數據截斷,假如講一個repeated的序列化後的數據做爲輸入給客戶端,客戶端會截取最後一個原子類型的字節。或者,若是是一個message類型的字段的話,合併全部的元素。
  11. 能夠修改字段默認值

 9. Package

PB建議在.proto文件開頭添加一個package說明符來避免不一樣message類型的名字衝突:

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

這樣,就可使用該package標示符來定義該message類型的字段:

?
1
2
3
4
5
6
7
8
9
message Foo {
 
     ...
 
     required foo.bar.Open open = 1;
 
     ...
 
}

不一樣語言,由於添加package標示符,生成的代碼也會有所不一樣,Java中,該package將會被用做java文件的package。若是不想這樣的話,也可在.proto文件中顯式指明package,該字段是:java_package。

譯自:https://developers.google.com/protocol-buffers/docs/proto

說實話,翻譯下來整個文章很是辛苦,並且都要敲代碼去親自試驗可否經過,因此若是您想轉載,很是歡迎,但請註明出處,也算是對俺辛苦的尊重~ 

原創做品,容許轉載,轉載時請務必以超連接形式標明文章 原始出處 、做者信息 石頭兒本聲明。不然將追究法律責任。http://shitouer.cn/2013/04/protocol-buffers-language-guide/
相關文章
相關標籤/搜索