1. 程式人生 > >Protocol Buffers(Protobuf) 官方文件--Protobuf語言指南

Protocol Buffers(Protobuf) 官方文件--Protobuf語言指南

約定:為方便書寫,ProtocolBuffers在下文中將已Protobuf代替。

本指南將向您描述如何使用protobuf定義i結構化Protobuf資料,包括.proto檔案語法和如何使用.proto檔案生成資料存取類。

作為一個參考指南,本文件將以示例的形式一步步向您介紹Protobuf的特點。您可以參考您所選擇的語言的示例。tutorial

--------------------------------------小小的分割線-----------------------------------------

定義一個訊息型別

首先,看一個非常簡單的例子,比如說你想定義一個 搜尋請求訊息 ,每個搜尋請求都有一個 查詢的字串(關鍵字:比如我們上百度搜索 《報告老闆》),和我們搜尋出來的一個感興趣的網頁,以及搜尋到的所有網頁總數。 來看看這個.proto檔案是如何定義的。

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

這個"搜尋請求"訊息指定了三個欄位(名稱/屬性 組合),每一個你想要包含在這型別的資訊內的東西,都必須有一個欄位,每個欄位有一個名稱和型別!

指定欄位型別

在上面的示例中,所有的欄位都是標量型別(scalar types):兩個整數(integers:page_number 和 result_per_page)和一個字串(string:query:查詢的關鍵字),不過你可以在你的欄位內指定符合型別。包括列舉型別(

enumerations)和其他的訊息型別

分配指定標籤號

如你所見,每個訊息的欄位都有一個唯一的數字標籤,這些標籤用來表示你的欄位在二進位制訊息(message binary format)中處的位置。並且一旦指定標籤號,在使用過程中是不可以更改的,標記這些標籤號在1-15的範圍內每個欄位需要使用1個位元組用來編碼這一個位元組包括欄位所在的位置和欄位的型別!(需要更多關於編碼的資訊請點選Protocol Buffer Encoding)。標籤號在16-2047需要使用2個位元組來編碼。所以你最好將1-15的標籤號為頻繁使用到的欄位所保留。如果將來可能會新增一些頻繁使用到的元素,記得留下一些1-15標籤號。

最小可指定的標籤號為1,最大的標籤號為229 - 1或者536870911。不能使用19000-19999的標籤號(FieldDescriptor::kFirstReservedNumber 至 FieldDescriptor::kLastReservedNumber) 這些標籤號是為protobuf內部實現所保留的,如果你在.proto檔案內使用了這些標籤號Protobuf編譯器將會報錯!

指定欄位規則

訊息欄位可以被指定為以下三種:

  • required: 完整的訊息內必須擁有此欄位。此欄位是必須擁有的 (雙方都要有)
  • optional: 完整的訊息內此欄位是可選的,可擁有也可以沒有(雙方可選)
  • repeated: 完整的訊息內本欄位的值可以擁有任意個,重複的值的次數會儲存下來。(雙方可選,陣列)

因為歷史的原因:repeated欄位如果是基本的數字型別的話會無法編碼。新的程式碼應該使用特殊的關鍵字[packed=true] 來使其得到有效的編碼.例如

repeated int32 samples = 4 [packed=true];

注意:你應該小心將欄位設定為required,如果你希望在某些情況下取消required欄位的讀寫,它將改變欄位為optional屬性,舊的的讀取方將會認為此訊息不完全。可能會無意的將其丟棄。你應該考慮自定義一個訊息檢查程式。google的一些工程師認為使用optinal欄位的好處大於required。但是顯然這個觀點並不是通用的。

新增更多的訊息型別

多個訊息型別可以定義在同一個.proto檔案內,這對定義多個有關聯的訊息是是十分有用的。例如,如果你想定義一個用於回覆SearchResponse訊息,你可以像這樣在.proto內新增。

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

message SearchResponse {
 ...
}

添加註釋

添加註釋的方式和C/C++是一樣的。使用//

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檔案會生成什麼?

當你使用protobuf編譯器編譯一個.proto檔案,它會生成在.proto內你描述的訊息型別的操作程式碼,這些程式碼是根據你所選擇的程式設計功能語言決定的。這些操作程式碼內包含了設定欄位值 和讀取欄位值,以及序列化到輸出流 和 從輸入流反序列化。

C++:編譯器會按照每個.proto檔案生成與其對應的.h和.cc檔案,每個訊息類似都有獨立的訊息操作類。

Java:編譯器將會生成一個.java檔案和一個操作類,此操作類為所有訊息型別所共有, 使用一個特別的Builder類為每個訊息型別例項化.

Python:有一點不同 – 編譯器會為每個訊息生成一個模組每個模組有一個靜態描述符, 該模組與一個元類在執行時建立一個需要的資料操作類。

從你所選擇語言的例程,你可以找到更多關於API的內容, 需要關於API的詳細資訊, 參考: API reference.

標量值型別

一個訊息的欄位如果要使用標量可使之為以下型別 –這個表格顯示了在.proto檔案內可以指定的型別, 與自動生成的相對型別!

.proto Type Notes C++ Type Java Type Python Type[2]
double double double float
float float float float
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int int
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long int/long[3]
uint32 Uses variable-length encoding. uint32 int[1] int/long[3]
uint64 Uses variable-length encoding. uint64 long[1] int/long[3]
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int int
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long int/long[3]
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int[1] int
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long[1] int/long[3]
sfixed32 Always four bytes. int32 int int
sfixed64 Always eight bytes. int64 long int/long[3]
bool bool boolean boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String str/unicode[4]
bytes May contain any arbitrary sequence of bytes. string ByteString str

你可以在Protocol Buffer Encoding.找到更多關於.這些型別如何編碼,如何序列化定義訊息的資訊!

[1] 在Java中, 無符號32位和64位整數與其有符號相對應, 最高位用來儲存符號!

[2] 在所有情況下, 設定某個欄位的值將會執行型別檢查確保其值是合法的!

[3] 64位或32位無符號整數在解碼中會以long來解碼, 給欄位賦值的時候可以是int.但是在所有情況下,賦值的時候會轉變為其目標型別 . 詳見 [2].

[4] Python的字串在解碼時候會以unicode來描述,但是同樣的可以給其賦值為ascii字串  (此乃弦外之音).

Optional 欄位與其預設值

如上所述,在描述一個訊息的時候可以用optional指定欄位約束,一個訊息可以包含也可以不包含optional元素。當一個訊息被解析,如果其沒有一個optional欄位,被解析的訊息物件就會將其相對的欄位設定為其欄位的預設值。這個預設值可以在描述訊息的時候被指定。例如。比如你想設定SearchRequest的 result_per_page的預設值為10.

optional int32 result_per_page = 3 [default = 10];

 如果一個optional欄位沒有被指定其預設值。其預設值被自動替換為:

1.字串:為空字串.

2.bool:為false.

3.數字型別:為0;

4.列舉值:為第一個列舉值

列舉值

當你定義訊息格式的時候, 也許你希望其中的一個欄位的的值為一個預定義的值類表中的一個. 比方說, 在SearchRequest訊息中你想定義一個 corpus 欄位, corpus欄位的值可以為:" UNIVERSALWEBIMAGESLOCALNEWSPRODUCTS 或者 VIDEO". 你可以非常簡單的給你的訊息新增一個列舉型別 - 一個列舉欄位型別其值指定被指定為一個常量的集合 (如果你嘗試賦值一個不一樣的值, 解析器將會認為這個欄位為未知欄位). 在下面的例子中 我們給corpus欄位指定為列舉型別與其可能的值 :

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。否則如果出現別名的話編譯器將會報錯! 

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  //不註釋這行的話會引發一個錯誤異常

列舉值的範圍必須在32位整數之內.列舉值的編碼使用可變長度的整數,負數會非常低效所以,不推薦使用。你可以在一個訊息內部定義一個列舉型別,比如上面的例子。或者也可以在訊息的外部定義。這些列舉型別是可以在.proto檔案內中重用的,你可以在訊息內定義個列舉型別。然後在不同的訊息型別中使用它!可以使用MessageType.EnumType來訪問。當你執行編譯器編譯.proto檔案中的列舉型別時,生成的程式碼會有一個相對應的列舉值(JAVA 或者C++),或者有一個特別的EnumDescriptor類(python)用於在執行時生成一個符號常量集合。

 待續....................