Protobuf 學習筆記

來源:互聯網
上載者:User

文章內容來源自Google官方文檔翻譯,詳見原文Language Guide。部分內容可能重複,望多見諒。

假設你想定義一個“搜尋請求”的訊息格式,每一個請求含有一個查詢字串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以採用如下的方式來定義訊息類型的.proto檔案了:

syntax = "proto3";message SearchRequest {  string query = 1;  int32 page_number = 2;  int32 result_per_page = 3;}

SearchRequest訊息格式有3個欄位,在訊息中承載的資料分別對應於每一個欄位。其中每個欄位都有一個名字和一種類型。

注意:

  • 檔案的第一行指定了你正在使用proto3文法:如果你沒有指定這個,編譯器會使用proto2。這個指定文法行必須是檔案的非空非注釋的第一個行。

訊息定義

1. 指定欄位類型

在上面的例子中,所有欄位都是標量類型:兩個整型(page_numberresult_per_page),一個string類型(query)。當然,你也可以為欄位指定其他的合成類型,包括枚舉(enumerations)或其他訊息類型。

2. 分配標識號

正如上述檔案格式,在訊息定義中,每個欄位都有唯一的一個數位識別碼符。這些標識符是用來在訊息的二進位格式中識別各個欄位的,一旦開始使用就不能夠再改變。

註:[1,15]之內的標識號在編碼的時候會佔用一個位元組。[16,2047]之內的標識號則佔用2個位元組。所以應該為那些頻繁出現的訊息元素保留 [1,15]之內的標識號。切記:要為將來有可能添加的、頻繁出現的標識號預留一些標識號。

最小的標識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto檔案中使用這些預留標識號,編譯時間就會警示。

3. 指定欄位規則

所指定的訊息欄位修飾符必須是如下之一:

  • required:一個格式良好的訊息一定要含有1個這種欄位。表示該值是必須要設定的;
  • optional:訊息格式中該欄位可以有0個或1個值(不超過1個)。
  • repeated:在一個格式良好的訊息中,這種欄位可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java中的List。

由於一些曆史原因,基本數實值型別的repeated的欄位並沒有被儘可能地高效編碼。在新的代碼中,使用者應該使用特殊選項[packed=true]來保證更高效的編碼。如:

repeated int32 samples = 4 [packed=true];

required是永久性的:在將一個欄位標識為required的時候,應該特別小心。如果在某些情況下不想寫入或者發送一個required的欄位,將原始該欄位修飾符更改為optional可能會遇到問題——舊版本的使用者會認為不含該欄位的訊息是不完整的,從而可能會無目的的拒絕解析。在這種情況下,你應該考慮編寫特別針對於應用程式的、自訂的訊息校正函數。

Google的一些工程師得出了一個結論:使用required弊多於利;他們更 願意使用optional和repeated而不是required。當然,這個觀點並不具有普遍性。

4. 添加更多訊息類型

在一個.proto檔案中可以定義多個訊息類型。在定義多個相關的訊息的時候,這一點特別有用——例如,如果想定義與SearchResponse訊息類型對應的回複訊息格式的話,你可以將它添加到相同的.proto檔案中,如:

message SearchRequest {  string query = 1;  int32 page_number = 2;  int32 result_per_page = 3;}message SearchResponse { ...}

5. 添加註釋

向.proto檔案添加註釋,可以使用C/C++/java風格的雙斜杠(//) 文法格式,如:

message SearchRequest {  string query = 1;  int32 page_number = 2;  // Which page number do we want?  int32 result_per_page = 3;  // Number of results to return per page.}

6. .proto檔案產生了什麼

當用protocolbuffer編譯器來運行.proto檔案時,編譯器將產生所選擇語言的代碼,這些代碼可以操作在.proto檔案中定義的訊息類型,包括擷取、設定欄位值,將訊息序列化到一個輸出資料流中,以及從一個輸入資料流中解析訊息。

  • 對go來說,編譯器會位每個訊息類型產生了一個.pd.go檔案。
  • 對C++來說,編譯器會為每個.proto檔案產生一個.h檔案和一個.cc檔案,.proto檔案中的每一個訊息有一個對應的類。
  • 對Python來說,有點不太一樣——Python編譯器為.proto檔案中的每個訊息類型產生一個含有靜態描述符的模組,,該模組與一個元類(metaclass)在運行時(runtime)被用來建立所需的Python資料訪問類。
  • 對於Objective-C來說,編譯器會為每個訊息類型產生了一個pbobjc.h檔案和pbobjcm檔案,.proto檔案中的每一個訊息有一個對應的類。

7. 標量數實值型別

一個標量訊息欄位可以自動產生的訪問類中定義的類型.

更多“序列化訊息時各種類型如何編碼”的資訊請看這裡

8. Optional的欄位和預設值

訊息描述中的一個元素可以被標記為“可選的”(optional)。一個格式良好的訊息可以包含0個或一個optional的元素。當解 析訊息時,如果它不包含optional的元素值,那麼解析出來的對象中的對應欄位就被置為預設值。預設值可以在訊息描述檔案中指定。例如,要為 SearchRequest訊息的result_per_page欄位指定預設值10,在定義訊息格式時如下所示:

optional int32 result_per_page = 3 [default = 10];

如果沒有為optional的元素指定預設值,就會使用與特定類型相關的預設值:對string來說,預設值是Null 字元串。對bool來說,預設值是false。對數實值型別來說,預設值是0。對枚舉來說,預設值是枚舉類型定義中的第一個值。

9. 枚舉

當需要定義一個訊息類型的時候,可能想為一個欄位指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest訊息添加一個 corpus欄位,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。

其實可以很容易地實現這一點:通過向訊息定義中添加一個枚舉(enum)就可以了。一個enum類型的欄位只能用指定的常量集中的一個值作為其值(如果嘗 試指定不同的值,解析器就會把它當作一個未知的欄位來對待)。

在下面的例子中,在訊息格式中添加了一個叫做Corpus的枚舉類型——它含有所有可能的值 ——以及一個類型為Corpus的欄位:

message SearchRequest {  string query = 1;  int32 page_number = 2;  int32 result_per_page = 3;  enum Corpus {    UNIVERSAL = 0;    WEB = 1;    IMAGES = 2;    LOCAL = 3;    NEWS = 4;    PRODUCTS = 5;    VIDEO = 6;  }  Corpus corpus = 4;}

如果給枚舉常量定義別名, 需要設定allow_alias option 為 true, 否則 protocol編譯器會產生錯誤資訊。

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.}

枚舉常量必須在32位整型值的範圍內。因為enum值是使用可變編碼方式的,對負數不夠高效,因此不推薦在enum中使用負數。

如上例所示,可以在 一個訊息定義的內部或外部定義枚舉——這些枚舉可以在.proto檔案中的任何訊息定義裡重用。當然也可以在一個訊息中聲明一個枚舉類型,而在另一個不同 的訊息中使用它——採用MessageType.EnumType的文法格式。

當對一個使用了枚舉的.proto檔案運行protocol buffer編譯器的時候,產生的程式碼中將有一個對應的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在運行時產生的類中建立一系列的整型值符號常量(symbolic constants)。

使用

message支援嵌套使用,作為另一message中的欄位類型

message SearchResponse {    repeated Result results = 1;}message Result {    string url = 1;    string title = 2;    repeated string snippets = 3;}

匯入定義(import)

可以使用import語句匯入使用其它描述檔案中聲明的類型

import "others.proto";

protocol buffer編譯器會在-I / --proto_path參數指定的目錄中尋找匯入的檔案,如果沒有指定該參數,預設在目前的目錄中尋找。

Message嵌套

栗子:

message SearchResponse {    message Result {        string url = 1;        string title = 2;        repeated string snippets = 3;    }    repeated Result results = 1;}

內部聲明的message類型名稱只可在內部直接使用,在外部參考需要前置父級message名稱,如Parent.Type

message SomeOtherMessage {    SearchResponse.Result result = 1;}

支援多層嵌套:

message Outer {                // Level 0    message MiddleAA {         // Level 1        message Inner {        // Level 2            int64 ival = 1;            bool  booly = 2;        }    }    message MiddleBB {         // Level 1        message Inner {        // Level 2            int32 ival = 1;            bool  booly = 2;        }    }}

Map類型

proto3 支援 map 型別宣告:

map<key_type, value_type> map_field = N;message Project {...}map<string, Project> projects = 1;
  • 鍵、實值型別可以是內建的標量類型,也可以是自訂message類型
  • 欄位不支援repeated屬性
  • 不要依賴map類型的欄位順序

包(Packages)

在.proto檔案中使用package聲明包名,避免命名衝突。

syntax = "proto3";package foo.bar;message Open {...}

在其他的訊息格式定義中可以使用包名+訊息名的方式來使用類型,如:

message Foo {    ...    foo.bar.Open open = 1;    ...}

在不同的語言中,包名定義對編譯後產生的程式碼的影響不同:

  • C++ 中:對應C++命名空間,例如Open會在命名空間foo::bar中
  • Java 中:package會作為Java包名,除非指定了option jave_package選項
  • Python 中:package被忽略
  • Go 中:預設使用package名作為包名,除非指定了option go_package選項
  • JavaNano 中:同Java
  • C# 中:package會轉換為駝峰式命名空間,如Foo.Bar,除非指定了option csharp_namespace選項

定義服務(Service)

如果想要將訊息類型用在RPC(遠程方法調用)系統中,可以在.proto檔案中定義一個RPC服務介面,protocol buffer編譯器會根據所選擇的不同語言產生服務介面代碼。

例如,想要定義一個RPC服務並具有一個方法,該方法接收SearchRequest並返回一個SearchResponse,此時可以在.proto檔案中進行如下定義:

service SearchService {    rpc Search (SearchRequest) returns (SearchResponse) {}}

產生的介面代碼作為用戶端與服務端的約定,服務端必須實現定義的所有介面方法,用戶端直接調用同名方法向服務端發起請求。

比較蛋疼的是即便業務上不需要參數也必須指定一個請求訊息,一般會定義一個空message。

選項(Options)

在定義.proto檔案時可以標註一系列的options。Options並不改變整個檔案聲明的含義,但卻可以影響特定環境下處理方式。完整的可用選項可以查google。

一些選項是檔案層級的,意味著它可以作用於頂層範圍,不包含在任何訊息內部、enum或服務定義中。一些選項是訊息層級的,可以用在訊息定義的內部。當然有些選項可以作用在欄位、enum類型、enum值、服務類型及服務方法中。

但是到目前為止,並沒有一種有效選項能作用於這些類型。

以下是一些常用的選擇:

  • java_package (file option):指定產生java類所在的包,如果在.proto檔案中沒有明確的聲明java_package,會使用預設包名。不需要產生java代碼時不起作用
  • java_outer_classname (file option):指定產生Java類的名稱,如果在.proto檔案中沒有明確聲明java_outer_classname,產生的class名稱將會根據.proto檔案的名稱採用駝峰式的命名方式進行產生。如(foo_bar.proto產生的java類名為FooBar.java),不需要產生java代碼時不起任何作用
  • objc_class_prefix (file option): 指定Objective-C類首碼,會前置在所有類和枚舉類型名之前。沒有預設值,應該使用3-5個大寫字母。注意所有2個字母的首碼是Apple保留的。

基本規範

  • 描述檔案以.proto做為檔案尾碼,除結構定義外的語句以分號結尾

  • 結構定義包括:message、service、enum

  • rpc方法定義結尾的分號可有可無

  • Message命名採用駝峰命名方式,欄位命名採用小寫字母加底線分隔方式

message SongServerRequest {    required string song_name = 1;}
  • Enums類型名採用駝峰命名方式,欄位命名採用大寫字母加底線分隔方式
enum Foo {    FIRST_VALUE = 1;    SECOND_VALUE = 2;}
  • Service與rpc方法名統一採用駝峰式命名

  • message對應golang中的struct,編譯產生go代碼後,欄位名會轉換為駝峰式

編譯

通過定義好的.proto檔案產生Java, Python, C++, Go, Ruby, JavaNano, Objective-C, or C# 代碼,需要安裝編譯器protoc。

參考Github項目 google/protobuf 安裝編譯器,Go語言需要同時安裝一個特殊的外掛程式:golang/protobuf。

運行命令:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

這裡只做參考就好,具體語言的編譯執行個體請參考詳細文檔,

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.