十五.ProtoBuf3的基礎總結

轉自: http://www.javashuo.com/article/p-zdwnwglv-hw.htmlphp

英文原文: 
Language Guide (proto3) 
中文出處: 
Protobuf語言指南 
[譯]Protobuf 語法指南 
中文出處是proto2的譯文,proto3的英文出現後在原來基礎上增改了,水平有限,還請指正html

這個指南描述瞭如何使用Protocol buffer 語言去描述你的protocol buffer 數據, 包括 .proto文件符號和如何從.proto文件生成類。包含了proto2版本的protocol buffer語言:對於老版本的proto3 符號,請見Proto2 Language Guide(以及中文譯本,抄了不少這裏的感謝下老版本的翻譯者)java

本文是一個參考指南——若是要查看如何使用本文中描述的多個特性的按部就班的例子,請在教程中查找須要的語言的教程。python

定義一個消息類型

先來看一個很是簡單的例子。假設你想定義一個「搜索請求」的消息格式,每個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。能夠採用以下的方式來定義消息類型的.proto文件了:git

syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
  • 文件的第一行指定了你正在使用proto3語法:若是你沒有指定這個,編譯器會使用proto2。這個指定語法行必須是文件的非空非註釋的第一個行。
  • SearchRequest消息格式有3個字段,在消息中承載的數據分別對應於每個字段。其中每一個字段都有一個名字和一種類型。

指定字段類型

在上面的例子中,全部字段都是標量類型:兩個整型(page_number和result_per_page),一個string類型(query)。固然,你也能夠爲字段指定其餘的合成類型,包括枚舉(enumerations)或其餘消息類型。github

分配標識號

正如你所見,在消息定義中,每一個字段都有惟一的一個數字標識符。這些標識符是用來在消息的二進制格式中識別各個字段的,一旦開始使用就不可以再改變。注:[1,15]以內的標識號在編碼的時候會佔用一個字節。[16,2047]以內的標識號則佔用2個字節。因此應該爲那些頻繁出現的消息元素保留 [1,15]以內的標識號。切記:要爲未來有可能添加的、頻繁出現的標識號預留一些標識號。golang

最小的標識號能夠從1開始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]( (從FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的標識號, Protobuf協議實現中對這些進行了預留。若是非要在.proto文件中使用這些預留標識號,編譯時就會報警。一樣你也不能使用早期保留的標識號。objective-c

指定字段規則

所指定的消息字段修飾符必須是以下之一:json

  • singular:一個格式良好的消息應該有0個或者1個這種字段(可是不能超過1個)。
  • repeated:在一個格式良好的消息中,這種字段能夠重複任意屢次(包括0次)。重複的值的順序會被保留。api

    在proto3中,repeated的標量域默認狀況蝦使用packed。

    你能夠了解更多的pakced屬性在Protocol Buffer 編碼

添加更多消息類型

在一個.proto文件中能夠定義多個消息類型。在定義多個相關的消息的時候,這一點特別有用——例如,若是想定義與SearchResponse消息類型對應的回覆消息格式的話,你能夠將它添加到相同的.proto文件中,如:

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

添加註釋

向.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. }
  • 1
  • 2
  • 3
  • 4
  • 5

保留標識符(Reserved)

若是你經過刪除或者註釋全部域,之後的用戶能夠重用標識號當你從新更新類型的時候。若是你使用舊版本加載相同的.proto文件這會致使嚴重的問題,包括數據損壞、隱私錯誤等等。如今有一種確保不會發生這種狀況的方法就是指定保留標識符(and/or names, which can also cause issues for JSON serialization不明白什麼意思),protocol buffer的編譯器會警告將來嘗試使用這些域標識符的用戶。

message Foo {
  reserved 2, 15, 9 to 11; reserved "foo", "bar"; }

 

注:不要在同一行reserved聲明中同時聲明域名字和標識號

從.proto文件生成了什麼?

當用protocol buffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼能夠操做在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。

  • 對C++來講,編譯器會爲每一個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每個消息有一個對應的類。
  • 對Java來講,編譯器爲每個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來建立消息類接口的)。
  • 對Python來講,有點不太同樣——Python編譯器爲.proto文件中的每一個消息類型生成一個含有靜態描述符的模塊,,該模塊與一個元類(metaclass)在運行時(runtime)被用來建立所需的Python數據訪問類。
  • 對go來講,編譯器會位每一個消息類型生成了一個.pd.go文件。
  • 對於Ruby來講,編譯器會爲每一個消息類型生成了一個.rb文件。
  • javaNano來講,編譯器輸出相似域java可是沒有Builder類
  • 對於Objective-C來講,編譯器會爲每一個消息類型生成了一個pbobjc.h文件和pbobjcm文件,.proto文件中的每個消息有一個對應的類。
  • 對於C#來講,編譯器會爲每一個消息類型生成了一個.cs文件,.proto文件中的每個消息有一個對應的類。

你能夠從以下的文檔連接中獲取每種語言更多API(proto3版本的內容很快就公佈)。API Reference

標量數值類型

一個標量消息字段能夠含有一個以下的類型——該表格展現了定義於.proto文件中的類型,以及與之對應的、在自動生成的訪問類中定義的類型:

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type
double   double double float float64 Float double float
float   float float float float32 Float float float
int32 使用變長編碼,對於負值的效率很低,若是你的域有可能有負值,請使用sint64替代 int32 int int int32 Fixnum 或者 Bignum(根據須要) int integer
uint32 使用變長編碼 uint32 int int/long uint32 Fixnum 或者 Bignum(根據須要) uint integer
uint64 使用變長編碼 uint64 long int/long uint64 Bignum ulong integer/string
sint32 使用變長編碼,這些編碼在負值時比int32高效的多 int32 int int int32 Fixnum 或者 Bignum(根據須要) int integer
sint64 使用變長編碼,有符號的整型值。編碼時比一般的int64高效。 int64 long int/long int64 Bignum long integer/string
fixed32 老是4個字節,若是數值老是比老是比228大的話,這個類型會比uint32高效。 uint32 int int uint32 Fixnum 或者 Bignum(根據須要) uint integer
fixed64 老是8個字節,若是數值老是比老是比256大的話,這個類型會比uint64高效。 uint64 long int/long uint64 Bignum ulong integer/string
sfixed32 老是4個字節 int32 int int int32 Fixnum 或者 Bignum(根據須要) int integer
sfixed64 老是8個字節 int64 long int/long int64 Bignum long integer/string
bool   bool boolean bool bool TrueClass/FalseClass bool boolean
string 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 string String str/unicode string String (UTF-8) string string
bytes 可能包含任意順序的字節數據。 string ByteString str []byte String (ASCII-8BIT) ByteString string

你能夠在文章Protocol Buffer 編碼中,找到更多「序列化消息時各類類型如何編碼」的信息。

  1. 在java中,無符號32位和64位整型被表示成他們的整型對應形似,最高位被儲存在標誌位中。
  2. 對於全部的狀況,設定值會執行類型檢查以確保此值是有效。
  3. 64位或者無符號32位整型在解碼時被表示成爲ilong,可是在設置時可使用int型值設定,在全部的狀況下,值必須符合其設置其類型的要求。
  4. python中string被表示成在解碼時表示成unicode。可是一個ASCIIstring能夠被表示成str類型。
  5. Integer在64位的機器上使用,string在32位機器上使用

默認值

當一個消息被解析的時候,若是被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應的域被設置位一個默認值,對於不一樣類型指定以下:

  • 對於strings,默認是一個空string
  • 對於bytes,默認是一個空的bytes
  • 對於bools,默認是false
  • 對於數值類型,默認是0
  • 對於枚舉,默認是第一個定義的枚舉值,必須爲0;
  • 對於消息類型(message),域沒有被設置,確切的消息是根據語言肯定的,詳見generated code guide

    對於可重複域的默認值是空(一般狀況下是對應語言中空列表)。

    注:對於標量消息域,一旦消息被解析,就沒法判斷域釋放被設置爲默認值(例如,例如boolean值是否被設置爲false)仍是根本沒有被設置。你應該在定義你的消息類型時很是注意。例如,好比你不該該定義boolean的默認值false做爲任何行爲的觸發方式。也應該注意若是一個標量消息域被設置爲標誌位,這個值不該該被序列化傳輸。

    查看generated code guide選擇你的語言的默認值的工做細節。

枚舉

當須要定義一個消息類型的時候,可能想爲一個字段指定某「預約義值序列」中的一個值。例如,假設要爲每個SearchRequest消息添加一個 corpus字段,而corpus的值多是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實能夠很容易地實現這一點:經過向消息定義中添加一個枚舉(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; }

如你所見,Corpus枚舉的第一個常量映射爲0:每一個枚舉類型必須將其第一個類型映射爲0,這是由於:

  • 必須有有一個0值,咱們能夠用這個0值做爲默認值。
  • 這個零值必須爲第一個元素,爲了兼容proto2語義,枚舉類的第一個值老是默認值。

    你能夠經過將不一樣的枚舉常量指定位相同的值。若是這樣作你須要將allow_alias設定位true,不然編譯器會在別名的地方產生一個錯誤信息。

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)。

在反序列化的過程當中,沒法識別的枚舉值會被保存在消息中,雖然這種表示方式須要依據所使用語言而定。在那些支持開放枚舉類型超出指定範圍以外的語言中(例如C++和Go),爲識別的值會被表示成所支持的整型。在使用封閉枚舉類型的語言中(Java),使用枚舉中的一個類型來表示未識別的值,而且可使用所支持整型來訪問。在其餘狀況下,若是解析的消息被序列號,未識別的值將保持原樣。

關於如何在你的應用程序的消息中使用枚舉的更多信息,請查看所選擇的語言generated code guide

使用其餘消息類型

你能夠將其餘消息類型用做字段類型。例如,假設在每個SearchResponse消息中包含Result消息,此時能夠在相同的.proto文件中定義一個Result消息類型,而後在SearchResponse消息中指定一個Result類型的字段,如:

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

導入定義

在上面的例子中,Result消息類型與SearchResponse是定義在同一文件中的。若是想要使用的消息類型已經在其餘.proto文件中已經定義過了呢? 
你能夠經過導入(importing)其餘.proto文件中的定義來使用它們。要導入其餘.proto文件的定義,你須要在你的文件中添加一個導入聲明,如:

import "myproject/other_protos.proto";
  • 1

默認狀況下你只能使用直接導入的.proto文件中的定義. 然而, 有時候你須要移動一個.proto文件到一個新的位置, 能夠不直接移動.proto文件, 只需放入一個僞 .proto 文件在老的位置, 而後使用import public轉向新的位置。import public 依賴性會經過任意導入包含import public聲明的proto文件傳遞。例如:

// 這是新的proto // All definitions are moved here

 

// 這是久的proto // 這是全部客戶端正在導入的包 import public "new.proto"; import "other.proto";

 

// 客戶端proto import "old.proto"; // 如今你可使用新久兩種包的proto定義了。
  • 1
  • 2
  • 3

經過在編譯器命令行參數中使用-I/--proto_pathprotocal 編譯器會在指定目錄搜索要導入的文件。若是沒有給出標誌,編譯器會搜索編譯命令被調用的目錄。一般你只要指定proto_path標誌爲你的工程根目錄就好。而且指定好導入的正確名稱就好。

使用proto2消息類型

在你的proto3消息中導入proto2的消息類型也是能夠的,反之亦然,而後proto2枚舉不能夠直接在proto3的標識符中使用(若是僅僅在proto2消息中使用是能夠的)。

嵌套類型

你能夠在其餘消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內,如:

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

若是你想在它的父消息類型的外部重用這個消息類型,你須要以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; } } }

更新一個消息類型

若是一個已有的消息格式已沒法知足新的需求——如,要在消息中添加一個額外的字段——可是同時舊版本寫的代碼仍然可用。不用擔憂!更新消息而不破壞已有代碼是很是簡單的。在更新時只要記住如下的規則便可。

  • 不要更改任何已有的字段的數值標識。
  • 若是你增長新的字段,使用舊格式的字段仍然能夠被你新產生的代碼所解析。你應該記住這些元素的默認值這樣你的新代碼就能夠以適當的方式和舊代碼產生的數據交互。類似的,經過新代碼產生的消息也能夠被舊代碼解析:只不過新的字段會被忽視掉。注意,未被識別的字段會在反序列化的過程當中丟棄掉,因此若是消息再被傳遞給新的代碼,新的字段依然是不可用的(這和proto2中的行爲是不一樣的,在proto2中未定義的域依然會隨着消息被序列化)
  • 非required的字段能夠移除——只要它們的標識號在新的消息類型中再也不使用(更好的作法多是重命名那個字段,例如在字段前添加「OBSOLETE_」前綴,那樣的話,使用的.proto文件的用戶未來就不會無心中從新使用了那些不應使用的標識號)。
  • int32, uint32, int64, uint64,和bool是所有兼容的,這意味着能夠將這些類型中的一個轉換爲另一個,而不會破壞向前、 向後的兼容性。若是解析出來的數字與對應的類型不相符,那麼結果就像在C++中對它進行了強制類型轉換同樣(例如,若是把一個64位數字看成int32來 讀取,那麼它就會被截斷爲32位的數字)。
  • sint32和sint64是互相兼容的,可是它們與其餘整數類型不兼容。
  • string和bytes是兼容的——只要bytes是有效的UTF-8編碼。
  • 嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。
  • fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。
  • 枚舉類型與int32,uint32,int64和uint64相兼容(注意若是值不相兼容則會被截斷),然而在客戶端反序列化以後他們可能會有不一樣的處理方式,例如,未識別的proto3枚舉類型會被保留在消息中,可是他的表示方式會依照語言而定。int類型的字段總會保留他們的

Any

Any類型消息容許你在沒有指定他們的.proto定義的狀況下使用消息做爲一個嵌套類型。一個Any類型包括一個能夠被序列化bytes類型的任意消息,以及一個URL做爲一個全局標識符和解析消息類型。爲了使用Any類型,你須要導入import google/protobuf/any.proto

import "google/protobuf/any.proto"; message ErrorStatus { string message = 1; repeated google.protobuf.Any details = 2; }

對於給定的消息類型的默認類型URL是type.googleapis.com/packagename.messagename

不一樣語言的實現會支持動態庫以線程安全的方式去幫助封裝或者解封裝Any值。例如在java中,Any類型會有特殊的pack()unpack()訪問器,在C++中會有PackFrom()UnpackTo()方法。

// Storing an arbitrary message type in Any. NetworkErrorDetails details = ...; ErrorStatus status; status.add_details()->PackFrom(details); // Reading an arbitrary message from Any. ErrorStatus status = ...; for (const Any& detail : status.details()) { if (detail.Is<NetworkErrorDetails>()) { NetworkErrorDetails network_error; detail.UnpackTo(&network_error); ... processing network_error ... } }

目前,用於Any類型的動態庫仍在開發之中 

若是你已經很熟悉proto2語法,使用Any替換拓展

Oneof

若是你的消息中有不少可選字段, 而且同時至多一個字段會被設置, 你能夠增強這個行爲,使用oneof特性節省內存.

Oneof字段就像可選字段, 除了它們會共享內存, 至多一個字段會被設置。 設置其中一個字段會清除其它字段。 你可使用case()或者WhichOneof() 方法檢查哪一個oneof字段被設置, 看你使用什麼語言了.

使用Oneof

爲了在.proto定義Oneof字段, 你須要在名字前面加上oneof關鍵字, 好比下面例子的test_oneof:

message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }

而後你能夠增長oneof字段到 oneof 定義中. 你能夠增長任意類型的字段, 可是不能使用repeated 關鍵字.

在產生的代碼中, oneof字段擁有一樣的 getters 和setters, 就像正常的可選字段同樣. 也有一個特殊的方法來檢查到底那個字段被設置. 你能夠在相應的語言API指南中找到oneof API介紹.

Oneof 特性

  • 設置oneof會自動清楚其它oneof字段的值. 因此設置屢次後,只有最後一次設置的字段有值.
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 若是解析器遇到同一個oneof中有多個成員,只有最會一個會被解析成消息。
  • oneof不支持repeated.
  • 反射API對oneof 字段有效.
  • 若是使用C++,需確保代碼不會致使內存泄漏. 下面的代碼會崩潰, 由於sub_message 已經經過set_name()刪除了
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
  • 在C++中,若是你使用Swap()兩個oneof消息,每一個消息,兩個消息將擁有對方的值,例如在下面的例子中,msg1會擁有sub_message而且msg2會有name
SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());

向後兼容性問題

當增長或者刪除oneof字段時必定要當心. 若是檢查oneof的值返回None/NOT_SET, 它意味着oneof字段沒有被賦值或者在一個不一樣的版本中賦值了。 你不會知道是哪一種狀況,由於沒有辦法判斷若是未識別的字段是一個oneof字段。

Tage 重用問題:

  • 將字段移入或移除oneof:在消息被序列號或者解析後,你也許會失去一些信息(有些字段也許會被清除)
  • 刪除一個字段或者加入一個字段:在消息被序列號或者解析後,這也許會清除你如今設置的oneof字段
  • 分離或者融合oneof:行爲與移動常規字段類似。

Map(映射)

若是你但願建立一個關聯映射,protocol buffer提供了一種快捷的語法:

map<key_type, value_type> map_field = N;

 

其中key_type能夠是任意Integer或者string類型(因此,除了floating和bytes的任意標量類型都是能夠的)value_type能夠是任意類型。

例如,若是你但願建立一個project的映射,每一個Projecct使用一個string做爲key,你能夠像下面這樣定義:

map<string, Project> projects = 3;

 

  • Map的字段能夠是repeated。
  • 序列化後的順序和map迭代器的順序是不肯定的,因此你不要指望以固定順序處理Map
  • 當爲.proto文件產生生成文本格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。
  • 從序列化中解析或者融合時,若是有重複的key則後一個key不會被使用,當從文本格式中解析map時,若是存在重複的key。

生成map的API如今對於全部proto3支持的語言均可用了,你能夠從API指南找到更多信息。

向後兼容性問題

map語法序列化後等同於以下內容,所以即便是不支持map語法的protocol buffer實現也是能夠處理你的數據的:

message MapFieldEntry {
  key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;

固然能夠爲.proto文件新增一個可選的package聲明符,用來防止不一樣的消息類型有命名衝突。如:

package foo.bar;
message Open { ... }

在其餘的消息格式定義中可使用包名+消息名的方式來定義域的類型,如:

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

包的聲明符會根據使用語言的不一樣影響生成的代碼。

  • 對於C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中; - 對於Java,包聲明符會變爲java的一個包,除非在.proto文件中提供了一個明確有java_package
  • 對於 Python,這個包聲明符是被忽略的,由於Python模塊是按照其在文件系統中的位置進行組織的。
  • 對於Go,包能夠被用作Go包名稱,除非你顯式的提供一個option go_package在你的.proto文件中。
  • 對於Ruby,生成的類能夠被包裝在內置的Ruby名稱空間中,轉換成Ruby所需的大小寫樣式 (首字母大寫;若是第一個符號不是一個字母,則使用PB_前綴),例如Open會在Foo::Bar名稱空間中。
  • 對於javaNano包會使用Java包,除非你在你的文件中顯式的提供一個option java_package
  • 對於C#包能夠轉換爲PascalCase後做爲名稱空間,除非你在你的文件中顯式的提供一個option csharp_namespace,例如,Open會在Foo.Bar名稱空間中

包及名稱的解析

Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每一個包會被看做是其父類包的內部類。固然對於 (foo.bar.Baz)這樣以「.」分隔的意味着是從最外圍開始的。

ProtocolBuffer編譯器會解析.proto文件中定義的全部類型名。 對於不一樣語言的代碼生成器會知道如何來指向每一個具體的類型,即便它們使用了不一樣的規則。

定義服務(Service)

若是想要將消息類型用在RPC(遠程方法調用)系統中,能夠在.proto文件中定義一個RPC服務接口,protocol buffer編譯器將會根據所選擇的不一樣語言生成服務接口代碼及存根。如,想要定義一個RPC服務並具備一個方法,該方法可以接收 SearchRequest並返回一個SearchResponse,此時能夠在.proto文件中進行以下定義:

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

最直觀的使用protocol buffer的RPC系統是gRPC一個由谷歌開發的語言和平臺中的開源的PRC系統,gRPC在使用protocl buffer時很是有效,若是使用特殊的protocol buffer插件能夠直接爲您從.proto文件中產生相關的RPC代碼。

若是你不想使用gRPC,也可使用protocol buffer用於本身的RPC實現,你能夠從proto2語言指南中找到更多信息

還有一些第三方開發的PRC實現使用Protocol Buffer。參考第三方插件wiki查看這些實現的列表。

JSON 映射

Proto3 支持JSON的編碼規範,使他更容易在不一樣系統之間共享數據,在下表中逐個描述類型。

若是JSON編碼的數據丟失或者其自己就是null,這個數據會在解析成protocol buffer的時候被表示成默認值。若是一個字段在protocol buffer中表示爲默認值,體會在轉化成JSON的時候編碼的時候忽略掉以節省空間。具體實現能夠提供在JSON編碼中可選的默認值。

proto3 JSON JSON示例 注意
message object {「fBar」: v, 「g」: null, …} 產生JSON對象,消息字段名能夠被映射成lowerCamelCase形式,而且成爲JSON對象鍵,null被接受併成爲對應字段的默認值
enum string 「FOO_BAR」 枚舉值的名字在proto文件中被指定
map object {「k」: v, …} 全部的鍵都被轉換成string
repeated V array [v, …] null被視爲空列表
bool true, false true, false  
string string 「Hello World!」  
bytes base64 string 「YWJjMTIzIT8kKiYoKSctPUB+」  
int32, fixed32, uint32 number 1, -10, 0 JSON值會是一個十進制數,數值型或者string類型都會接受
int64, fixed64, uint64 string 「1」, 「-10」 JSON值會是一個十進制數,數值型或者string類型都會接受
float, double number 1.1, -10.0, 0, 「NaN」, 「Infinity」 JSON值會是一個數字或者一個指定的字符串如」NaN」,」infinity」或者」-Infinity」,數值型或者字符串都是可接受的,指數符號也能夠接受
Any object {「@type」: 「url」, 「f」: v, … } 若是一個Any保留一個特上述的JSON映射,則它會轉換成一個以下形式:{"@type": xxx, "value": yyy}不然,該值會被轉換成一個JSON對象,@type字段會被插入所指定的肯定的值
Timestamp string 「1972-01-01T10:00:20.021Z」 使用RFC 339,其中生成的輸出將始終是Z-歸一化啊的,而且使用0,3,6或者9位小數
Duration string 「1.000340012s」, 「1s」 生成的輸出老是0,3,6或者9位小數,具體依賴於所須要的精度,接受全部能夠轉換爲納秒級的精度
Struct object { … } 任意的JSON對象,見struct.proto
Wrapper types various types 2, 「2」, 「foo」, true, 「true」, null, 0, … 包裝器在JSON中的表示方式相似於基本類型,可是容許nulll,而且在轉換的過程當中保留null
FieldMask string 「f.fooBar,h」 見fieldmask.proto
ListValue array [foo, bar, …]  
Value value   任意JSON值
NullValue null   JSON null

選項

在定義.proto文件時可以標註一系列的options。Options並不改變整個文件聲明的含義,但卻可以影響特定環境下處理方式。完整的可用選項能夠在google/protobuf/descriptor.proto找到。

一些選項是文件級別的,意味着它能夠做用於最外範圍,不包含在任何消息內部、enum或服務定義中。一些選項是消息級別的,意味着它能夠用在消息定義的內部。固然有些選項能夠做用在域、enum類型、enum值、服務類型及服務方法中。到目前爲止,並無一種有效的選項能做用於全部的類型。

以下就是一些經常使用的選擇:

  • java_package (文件選項) :這個選項代表生成java類所在的包。若是在.proto文件中沒有明確的聲明java_package,就採用默認的包名。固然了,默認方式產生的 java包名並非最好的方式,按照應用名稱倒序方式進行排序的。若是不須要產生java代碼,則該選項將不起任何做用。如:
option java_package = "com.example.foo";
  • java_outer_classname (文件選項): 該選項代表想要生成Java類的名稱。若是在.proto文件中沒有明確的java_outer_classname定義,生成的class名稱將會根據.proto文件的名稱採用駝峯式的命名方式進行生成。如(foo_bar.proto生成的java類名爲FooBar.java),若是不生成java代碼,則該選項不起任何做用。如:
option java_outer_classname = "Ponycopter";
  • optimize_for(文件選項): 能夠被設置爲 SPEED, CODE_SIZE,或者LITE_RUNTIME。這些值將經過以下的方式影響C++及java代碼的生成: 
    • SPEED (default): protocol buffer編譯器將經過在消息類型上執行序列化、語法分析及其餘通用的操做。這種代碼是最優的。
    • CODE_SIZE: protocol buffer編譯器將會產生最少許的類,經過共享或基於反射的代碼來實現序列化、語法分析及各類其它操做。採用該方式產生的代碼將比SPEED要少得多, 可是操做要相對慢些。固然實現的類及其對外的API與SPEED模式都是同樣的。這種方式常常用在一些包含大量的.proto文件並且並不盲目追求速度的 應用中。
    • LITE_RUNTIME: protocol buffer編譯器依賴於運行時核心類庫來生成代碼(即採用libprotobuf-lite 替代libprotobuf)。這種核心類庫因爲忽略了一 些描述符及反射,要比全類庫小得多。這種模式常常在移動手機平臺應用多一些。編譯器採用該模式產生的方法實現與SPEED模式不相上下,產生的類經過實現 MessageLite接口,但它僅僅是Messager接口的一個子集。
option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件選項):對於C++產生的代碼啓用arena allocation
  • objc_class_prefix(文件選項):設置Objective-C類的前綴,添加到全部Objective-C今後.proto文件產生的類和枚舉類型。沒有默認值,所使用的前綴應該是蘋果推薦的3-5個大寫字符,注意2個字節的前綴是蘋果所保留的。
  • deprecated(字段選項):若是設置爲true則表示該字段已經被廢棄,而且不該該在新的代碼中使用。在大多數語言中沒有實際的意義。在java中,這回變成@Deprecated註釋,在將來,其餘語言的代碼生成器也許會在字標識符中產生廢棄註釋,廢棄註釋會在編譯器嘗試使用該字段時發出警告。若是字段沒有被使用你也不但願有新用戶使用它,嘗試使用保留語句替換字段聲明。
int32 old_field = 6 [deprecated=true];

自定義選項

ProtocolBuffers容許自定義並使用選項。該功能應該屬於一個高級特性,對於大部分人是用不到的。若是你的確但願建立本身的選項,請參看 Proto2 Language Guide。注意建立自定義選項使用了拓展,拓展只在proto3中可用。

生成訪問類

能夠經過定義好的.proto文件來生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代碼,須要基於.proto文件運行protocol buffer編譯器protoc。若是你沒有安裝編譯器,下載安裝包並遵守README安裝。對於Go,你還須要安裝一個特殊的代碼生成器插件。你能夠經過GitHub上的protobuf庫找到安裝過程

經過以下方式調用protocol編譯器:

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
  • IMPORT_PATH聲明瞭一個.proto文件所在的解析import具體目錄。若是忽略該值,則使用當前目錄。若是有多個目錄則能夠屢次調用--proto_path,它們將會順序的被訪問並執行導入。-I=IMPORT_PATH--proto_path的簡化形式。
  • 固然也能夠提供一個或多個輸出路徑: 
    • --cpp_out 在目標目錄DST_DIR中產生C++代碼,能夠在C++代碼生成參考中查看更多。
    • --java_out 在目標目錄DST_DIR中產生Java代碼,能夠在 Java代碼生成參考中查看更多。
    • --python_out 在目標目錄 DST_DIR 中產生Python代碼,能夠在Python代碼生成參考中查看更多。
    • --go_out 在目標目錄 DST_DIR 中產生Go代碼,能夠在GO代碼生成參考中查看更多。
    • --ruby_out在目標目錄 DST_DIR 中產生Go代碼,參考正在製做中。
    • --javanano_out在目標目錄DST_DIR中生成JavaNano,JavaNano代碼生成器有一系列的選項用於定製自定義生成器的輸出:你能夠經過生成器的README查找更多信息,JavaNano參考正在製做中。
    • --objc_out在目標目錄DST_DIR中產生Object代碼,能夠在Objective-C代碼生成參考中查看更多。
    • --csharp_out在目標目錄DST_DIR中產生Object代碼,能夠在C#代碼生成參考中查看更多。
    • --php_out在目標目錄DST_DIR中產生Object代碼,能夠在PHP代碼生成參考中查看更多。

做爲一個方便的拓展,若是DST_DIR以.zip或者.jar結尾,編譯器會將輸出寫到一個ZIP格式文件或者符合JAR標準的.jar文件中。注意若是輸出已經存在則會被覆蓋,編譯器尚未智能到能夠追加文件。 - 你必須提議一個或多個.proto文件做爲輸入,多個.proto文件能夠只指定一次。雖然文件路徑是相對於當前目錄的,每一個文件必須位於其IMPORT_PATH下,以便每一個文件能夠肯定其規範的名稱。

相關文章
相關標籤/搜索