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)用於在運行時生成一個符號常量集合。

更多關於枚舉類型的信息查詢 generated code guide 選擇你使用的語言。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章