Protobuf 語言指南(proto3)

Protobuf 語言指南(proto3)

Protocol Buffer是Google的語言中立的,平臺中立的,可擴展機制的,用於序列化結構化數據 - 對比XML,但更小,更快,更簡單。您能夠定義數據的結構化,而後可使用特殊生成的源代碼輕鬆地在各類數據流中使用各類語言編寫和讀取結構化數據。php

定義消息類型

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

1 syntax = "proto3";
2 
3 message SearchRequest {
4   string query = 1;
5   int32 page_number = 2;
6   int32 result_per_page = 3;
7 }
  • 該文件的第一行指定您正在使用proto3語法:若是您不這樣作,protobuf 編譯器將假定您正在使用proto2。這必須是文件的第一個非空的非註釋行。java

  • 所述SearchRequest消息定義了三個字段(名稱/值對),對應着我須要的消息內容。每一個字段都有一個名稱和類型。python

指定字段類型

在上面的示例中,全部字段都是標量類型:兩個整數(page_numberresult_per_page)和一個字符串(query)。可是,您還能夠爲字段指定合成類型,包括枚舉和其餘消息類型。ios

分配標識號

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

最小的標識號能夠從1開始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。若是非要在.proto文件中使用這些預留標識號,編譯時就會報錯。github

指定字段規則

消息字段能夠是如下之一:golang

  • 單數:格式良好的消息能夠包含該字段中的零個或一個(但不超過一個)。objective-c

  • repeated:此字段能夠在格式良好的消息中重複任意次數(包括零)。將保留重複值的順序。spring

在proto3中,repeated數字類型的字段默認使用packed編碼。

packed您能夠在協議緩衝區編碼中找到有關編碼的更多信息。

添加更多消息類型

能夠在單個.proto文件中定義多種消息類型。當你要定義多個相關消息時,這就頗有用 了。 例如,若是要定義與SearchResponse消息類型對應的回覆消息格式,那麼我在同一個.proto文件中:

1   message SearchRequest {
2     string query = 1;
3     int32 page_number = 2;
4     int32 result_per_page = 3;
5   }
6 7   message SearchResponse {
8    ...
9   }

 

添加註釋

要爲.proto文件添加註釋,請使用C / C ++ - 樣式///* ... */語法。

1  / * SearchRequest表示搜索查詢,帶有分頁選項
2    *代表響應中包含哪些結果。* /
3 4   message SearchRequest {
5     string query = 1;
6     int32 page_number = 2; //咱們想要哪一個頁碼?
7     int32 result_per_page = 3; //每頁返回的結果數。
8   }

 

保留字段

當你在某次更新消息中屏蔽或者刪除了一個字段的話,將來的使用着可能在他們的更新中重用這個標籤數字來標記他們本身的字段。而後當他們加載舊的消息的時候就會出現不少問題,包括數據衝突,隱藏的bug等等。指定這個字段的標籤數字(或者名字,名字可能在序列化爲JSON的時候可能衝突)標記爲reserved來保證他們不會再次被使用。若是之後的人試用的話protobuf編譯器會提示出錯。

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

 

注意一個reserved字段不能既有標籤數字又有名字。

.proto文件最終生成什麼

當你使用protoc 來編譯一個.proto文件的時候,編譯器將利用你在文件中定義的類型生成你打算使用的語言的代碼文件。生成的代碼包括getting setting 接口和序列化,反序列化接口。

  • 對於C ++,編譯器會從每一個.proto文件生成一個.h和一個.cc文件,併爲您文件中描述的每種消息類型提供一個類。

  • 對於Java,編譯器生成一個.java文件,其中包含每種消息類型的類,以及Builder用於建立消息類實例的特殊類。

  • Python有點不一樣 - Python編譯器生成一個模塊,其中包含每一個消息類型的靜態描述符,而後,用一個元類在運行時建立必要的Python數據訪問類。

  • 對於Go,編譯器會爲.pb.go文件中的每種消息類型生成一個類型的文件。

  • 對於Ruby,編譯器生成一個.rb包含消息類型的Ruby模塊的文件。

  • 對於Objective-C,編譯器從每一個.proto文件生成一個pbobjc.h和一個pbobjc.m文件,其中包含文件中描述的每種消息類型的類。

  • 對於C#,編譯器會從每一個.proto文件生成一個.cs文件,其中包含文件中描述的每種消息類型的類。

您能夠按照所選語言的教程(即將推出的proto3版本)瞭解有關爲每種語言使用API的更多信息。有關更多API詳細信息,請參閱相關API參考(proto3版本即將推出)。

標量值類型

標量消息字段能夠具備如下類型之一 - 該表顯示.proto文件中指定的類型,以及自動生成的類中的相應類型:

.proto type notes C ++ type Java type Python type [2] Type Ruby type C# type PHP type
double   double double float float64 float double float
float   float float float FLOAT32 float float float
INT32 使用可變長度編碼。編碼負數的效率低 - 若是您的字段可能有負值,請改用sint32。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
Int64 使用可變長度編碼。編碼負數的效率低 - 若是您的字段可能有負值,請改用sint64。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
UINT32 使用可變長度編碼。 UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
UINT64 使用可變長度編碼。 UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
SINT32 使用可變長度編碼。簽名的int值。這些比常規int32更有效地編碼負數。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sint64 使用可變長度編碼。簽名的int值。這些比常規int64更有效地編碼負數。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
fixed32 老是四個字節。若是值一般大於2 28,則比uint32更有效。 UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
fixed64 老是八個字節。若是值一般大於2 56,則比uint64更有效。 UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
sfixed32 老是四個字節。 INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sfixed64 老是八個字節。 Int64 long int / long [3] Int64 TWINS long Integer/string[5]
Boolean   Boolean Boolean Boolean Boolean TrueClass / FalseClass Boolean Boolean
string 字符串必須始終包含UTF-8編碼或7位ASCII文本。 string string str / unicode[4] string String (UTF-8) string string
byte 能夠包含任意字節序列。 string Byte string Strait []byte String (ASCII-8BIT) Byte string string

protobuf 編碼序列化消息時,您能夠找到有關如何編碼這些類型的更多信息。

[1]在Java中,無符號的32位和64位整數使用它們的帶符號對應表示,最高位只是存儲在符號位中。

[2]在全部狀況下,將值設置爲字段將執行類型檢查以確保其有效。

[3] 64位或無符號32位整數在解碼時始終表示爲long,但若是在設置字段時給出int,則能夠爲int。在全部狀況下,該值必須適合設置時表示的類型。見[2]。

[4] Python字符串在解碼時表示爲unicode,但若是給出了ASCII字符串,則能夠是str(這可能會發生變化)。

[5] Integer用於64位計算機,字符串用於32位計算機。

默認值

解析消息時,若是編碼消息不包含特定的單數元素,則解析對象中的相應字段將設置爲該字段的默認值。這些默認值是特定於類型的:

  • 對於字符串,默認值爲空字符串。

  • 對於字節,默認值爲空字節。

  • 對於bools,默認值爲false。

  • 對於數字類型,默認值爲零。

  • 對於枚舉,默認值是第一個定義的枚舉值,該必須爲0。

  • 對於消息字段,未設置該字段。它的確切值取決於語言。有關詳細信息, 請參閱生成的代碼指

重複字段的默認值爲空(一般是相應語言的空列表)。

請注意,對於標量消息字段,一旦解析了消息,就沒法肯定字段是否顯式設置爲默認值(例如,是否設置了布爾值false)或者根本沒有設置:您應該記住這一點在定義消息類型時。例如,false若是您不但願默認狀況下也發生這種行爲,那麼在設置爲時,沒有一個布爾值能夠啓用某些行爲。還要注意的是,若是一個標消息字段設置爲默認值,該值將不會在電線上連載。

有關默認值如何在生成的代碼中工做的更多詳細信息,請參閱所選語言的生成代碼指南

枚舉

當你定義一個消息的時候,你可能但願它其中的某個字段必定是預先定義好的一組值中的一個。你如說我要在SearchRequest中添加corpus字段。它只能是 UNIVERSAL, WEB , IMAGES , LOCAL, NEWS ,PRODUCTS, 或者 VIDEO 。你能夠很簡單的在你的消息中定義一個枚舉而且定義corpus字段爲枚舉類型,若是這個字段給出了一個再也不枚舉中的值,那麼解析器就會把它看成一個未知的字段。

在下面的示例中,咱們添加了一個帶有全部可能值的枚舉方法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做爲數字默認值

  • 零值必須是第一個元素,以便與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值在線上使用varint編碼,所以負值效率低,所以不建議使用。您能夠enum在消息定義中定義s,如上例所示,enum也能夠在外部定義 - 這些能夠在.proto文件的任何消息定義中重用。您還可使用enum語法將一個消息中聲明的類型用做另外一個消息中的字段類型。 *MessageType*.*EnumType*

若是你想要使用在消息內定義的枚舉的話,使用語法 MessageType.EnumType 在你編譯帶有枚舉的.proto文件的時候,若是生成的是C++或者Java代碼, 那麼生成的代碼中會有對應的枚舉。

在反序列化期間,將在消息中保留沒法識別的枚舉值,可是當反序列化消息時,如何表示這種值取決於語言。在支持具備超出指定符號範圍的值的開放枚舉類型的語言中,例如C ++和Go,未知的枚舉值僅做爲其基礎整數表示存儲。在具備封閉枚舉類型(如Java)的語言中,枚舉中的大小寫用於表示沒法識別的值,而且可使用特殊訪問器訪問基礎整數。在任何一種狀況下,若是消息被序列化,則仍然會使用消息序列化沒法識別的值。

有關如何enum在應用程序中使用消息的詳細信息,請參閱所選語言的生成代碼指南

保留值

若是經過徹底刪除枚舉條目或將其註釋掉來更新枚舉類型,則將來用戶能夠在對類型進行本身的更新時重用該數值。若是之後加載相同的舊版本,這可能會致使嚴重問題.proto,包括數據損壞,隱私錯誤等。確保不會發生這種狀況的一種方法是指定已刪除條目的數值(和/或名稱,這也可能致使JSON序列化問題)reserved。若是未來的任何用戶嘗試使用這些標識符,協議緩衝編譯器將會抱怨。您可使用max關鍵字指定保留的數值範圍達到最大可能值。

enum Foo {
    reserved 2, 15, 9 to 11, 40 to max;
    reserved "FOO", "BAR";
  }

 

請注意,您不能在同一reserved語句中混合字段名稱和數值。

使用其餘消息類型

您可使用其餘消息類型做爲字段類型。例如,假設你想包括Result每一個消息的SearchResponse消息-要作到這一點,你能夠定義一個Result在同一個消息類型.proto,而後指定類型的字段ResultSearchResponse

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

 

導入定義

在上面的示例中,Result消息類型在同一文件中定義SearchResponse,若是要用做字段類型的消息類型已在另外一個.proto文件中定義,該怎麼辦?

您能夠.proto經過導入來使用其餘文件中的定義。要導入其餘.proto的定義,請在文件頂部添加import語句:

import「myproject / other_protos.proto」;

 

默認狀況下,您只能使用直接導入.proto文件中的定義。可是,有時您可能須要將.proto文件移動到新位置。.proto如今,您能夠.proto在舊位置放置一個虛擬文件,以使用該import public概念將全部導入轉發到新位置,而不是直接移動文件並在一次更改中更新全部調用站點。import public任何導入包含該import public語句的proto的人均可以傳遞依賴關係。例如:

 // new.proto
  // All definitions are moved here
  // old.proto
  //This is the proto that all clients are importing.
  import publicnew.proto」;
  import「other.proto」;
  // client.proto
  import "old.proto";
  //您使用old.proto和new.proto中的定義,但不使用other.proto

 

協議編譯器使用-I/ --proto_pathflag 在協議編譯器命令行中指定的一組目錄中搜索導入的文件 。若是沒有給出標誌,它將查找調用編譯器的目錄。一般,您應該將--proto_path標誌設置爲項目的根目錄,並對全部導入使用徹底限定名稱。

使用proto2消息類型

能夠導入proto2消息類型並在proto3消息中使用它們,反之亦然。可是,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;
      }
    }
  }

 

更新消息類型

若是現有的消息類型再也不知足您的全部需求 - 例如,您但願消息格式具備額外的字段 - 但您仍然但願使用使用舊格式建立的代碼,請不要擔憂!在不破壞任何現有代碼的狀況下更新消息類型很是簡單。請記住如下規則:

  • 請勿更改任何現有字段的字段編號。

  • 若是添加新字段,則使用「舊」消息格式按代碼序列化的任何消息仍可由新生成的代碼進行解析。您應該記住這些元素的默認值,以便新代碼能夠正確地與舊代碼生成的消息進行交互。一樣,您的新代碼建立的消息能夠由舊代碼解析:舊的二進制文件在解析時只是忽略新字段。有關詳細信息,請參閱「 未知字段」部分

  • 只要在更新的消息類型中再也不使用字段編號,就能夠刪除字段。您可能但願重命名該字段,可能添加前綴「OBSOLETE_」,或者保留字段編號,以便您的將來用戶.proto不會意外地重複使用該編號。

  • int32uint32int64uint64,和bool都是兼容的-這意味着你能夠改變這些類型到另外一個的一個場不破壞forwards-或向後兼容。若是從導線中解析出一個不符合相應類型的數字,您將得到與在C ++中將該數字轉換爲該類型相同的效果(例如,若是將64位數字做爲int32讀取,它將被截斷爲32位)。

  • sint32而且sint64彼此兼容但與其餘整數類型兼容。

  • string``bytes只要字節是有效的UTF-8 ,它們是兼容的。

  • bytes若是字節包含消息的編碼版本,則嵌入消息是兼容的。

  • fixed32與兼容sfixed32,並fixed64sfixed64

  • enum與兼容int32uint32int64,和uint64電線格式條款(注意,若是他們不適合的值將被截斷)。但請注意,在反序列化消息時,客戶端代碼可能會以不一樣方式對待它們:例如,enum將在消息中保留未識別的proto3 類型,但在反序列化消息時如何表示這種類型取決於語言。Int字段老是保留它們的價值。

  • 將單個值更改成 成員oneof是安全且二進制兼容的。oneof若是您肯定沒有代碼一次設置多個字段,則將多個字段移動到新字段多是安全的。將任何字段移動到現有字段oneof並不安全。

未知字段

未知字段是格式良好的協議緩衝區序列化數據,表示解析器沒法識別的字段。例如,當舊二進制文件解析具備新字段的新二進制文件發送的數據時,這些新字段將成爲舊二進制文件中的未知字段。

最初,proto3消息在解析期間老是丟棄未知字段,但在3.5版本中,咱們從新引入了保存未知字段以匹配proto2行爲。在版本3.5及更高版本中,未知字段在解析期間保留幷包含在序列化輸出中。

任何

Any消息類型,可使用郵件做爲嵌入式類型,而沒必要本身.proto定義。一個Any含有任意的序列化消息bytes,以充當一個全局惟一標識符和解析到該消息的類型的URL一塊兒。要使用該Any類型,您須要導入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*

不一樣的語言實現將支持運行時庫傭工類型安全的方式打包和解包的任何值-例如,在Java中,任何類型都會有特殊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

要在您中定義oneof,請.proto使用oneof關鍵字後跟您的oneof名稱,在這種狀況下test_oneof

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

 

而後,將oneof字段添加到oneof定義中。您能夠添加任何類型的字段,但不能使用repeated字段。

在生成的代碼中,oneof字段與常規字段具備相同的getter和setter。您還可使用特殊方法檢查oneof中的值(若是有)。您能夠在相關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());

     

  • 若是解析器在線路上遇到同一個oneof的多個成員,則在解析的消息中僅使用看到的最後一個成員。

  • 一個不可能repeated

  • Reflection API適用於其中一個字段。

  • 若是您使用的是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()兩個消息與oneofs,每一個消息最終將與另外一個消息結果:在下面的例子中,msg1將有一個sub_messagemsg2並將有一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返回的值None/ NOT_SET,這可能意味着oneof還沒有設置或已在不一樣版本的oneof的被設置爲一個字段。沒有辦法區分,由於沒有辦法知道線上的未知字段是不是其中一個成員。

標籤重用問題

  • 將字段移入或移出oneof:在序列化和解析消息後,您可能會丟失一些信息(某些字段將被清除)。可是,您能夠安全地將單個字段移動到新的 oneof中,而且若是已知只有一個字段被設置,則能夠移動多個字段。

  • 刪除oneof字段並將其添加回:在序列化和解析消息後,這可能會清除當前設置的oneof字段。

  • 拆分或合併oneof:這與移動常規字段有相似的問題。

Maps

若是要在數據定義中建立關聯映射,protobuf提供了一種方便的快捷方式語法:

map < key_type ,value_type > map_field = N ;

 

這裏key_type能夠是任意整形或者字符串。而value_tpye 能夠是任意類型。 舉個例子,若是你打算建立一個Project表,每一個Project關聯到一個字符串上的話 :

 map < string ,Project > projects = 3 ;  

 

  • map字段不能repeated

  • map值的有線格式排序和地圖迭代排序未定義,所以您不能依賴於特定順序的map項目。

  • .proto生成文本格式時,地圖按鍵排序。數字鍵按數字排序。

  • 從線路解析或合併時,若是有重複的映射鍵,則使用最後看到的鍵。從文本格式解析映射時,若是存在重複鍵,則解析可能會失敗。

  • 若是爲映射字段提供鍵但沒有值,則字段序列化時的行爲取決於語言。在C ++,Java和Python中,類型的默認值是序列化的,而在其餘語言中沒有任何序列化。

生成的地圖API目前可用於全部proto3支持的語言。您能夠在相關API參考中找到有關所選語言的地圖API的更多信息。

向後兼容性

在通信中,map等價與下面的定義, 這樣不支持Map的版本也能夠解析你的消息:

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

 

任何支持映射的協議緩衝區實現都必須生成和接受上述定義能夠接受的數據。

您能夠向.proto文件添加package可選說明符,以防止協議消息類型之間的名稱衝突。

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

 

而後,您能夠在定義消息類型的字段時使用包說明符:

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

 

包名字的實現取決於你工做的具體編程語言:

  • C ++中,生成的類包含在C ++命名空間中。例如,Open將在命名空間中foo::bar

  • Java中,該包用做Java包,除非您option java_package.proto文件中明確提供了該包。

  • Python中,package指令被忽略,由於Python模塊是根據它們在文件系統中的位置進行組織的。

  • Go中,該包用做Go包名稱,除非您option go_package.proto文件中明確提供。

  • Ruby中,生成的類包含在嵌套的Ruby命名空間內,轉換爲所需的Ruby大寫形式(首字母大寫;若是第一個字符不是字母,PB_則前置)。例如,Open將在命名空間中Foo::Bar

  • C#中,包轉換爲PascalCase後用做命名空間,除非您option csharp_namespace.proto文件中明確提供。例如,Open將在命名空間中Foo.Bar

包和名稱解析

協議緩衝區語言中的類型名稱解析與C ++相似:首先搜索最裏面的範圍,而後搜索下一個範圍,依此類推,每一個包被認爲是其父包的「內部」。一個領先的'。' (例如,.foo.bar.Baz)意味着從最外層的範圍開始。

protobuf 編譯器經過解析導入的.proto文件來解析全部類型名稱。每種語言的代碼生成器都知道如何使用該語言引用每種類型,即便它具備不一樣的範圍規則。

定義服務

若是要將消息類型與RPC(遠程過程調用)系統一塊兒使用,則能夠在.proto文件中定義RPC服務接口,protobuf 編譯器將使用您選擇的語言生成服務接口代碼和存根。所以,例如,若是要定義RPC服務請求方法爲:SearchRequest和返回方法爲:SearchResponse,能夠.proto按以下方式在文件中定義它:

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

 

與協議緩衝區一塊兒使用的最簡單的RPC系統是gRPC:一種由Google開發的,平臺中立的開源RPC系統。gRPC特別適用於protobuf,並容許在您的.proto文件中使用特殊的protobuf 編譯器插件直接生成相關的RPC代碼。

若是您不想使用gRPC,也能夠將protobuf與您本身的RPC實現一塊兒使用。您能夠在Proto2語言指南中找到更多相關信息。

還有一些正在進行的第三方項目使用Protocol Buffers開發RPC實現。有關咱們瞭解的項目的連接列表,請參閱第三方加載項wiki頁面

JSON映射

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

若是JSON編碼數據中缺乏值null,或者其值爲,則在解析爲協議緩衝區時,它將被解釋爲適當的默認值。若是字段在協議緩衝區中具備默認值,則默認狀況下將在JSON編碼數據中省略該字段以節省空間。實現能夠提供用於在JSON編碼的輸出中發出具備默認值的字段的選項。

proto3 JSON JSON示例 筆記
message object {"fooBar": v, "g": null,…} 生成JSON對象。消息字段名稱映射到小寫駝峯併成爲JSON對象鍵。若是json_name指定了field選項,則指定的值將用做鍵。解析器接受小寫駝峯名稱(或json_name選項指定的名稱)和原始proto字段名稱。null是全部字段類型的可接受值,並將其視爲相應字段類型的默認值。
eunm String "FOO_BAR" 使用proto中指定的枚舉值的名稱。解析器接受枚舉名稱和整數值。
map<K,V> object {"k": v, …} 全部鍵都轉換爲字符串。
repeated V. array [v, …] null 被接受爲空列表[]。
bool true,false true, false  
string string "Hello World!"  
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" JSON值將是使用帶填充的標準base64編碼編碼爲字符串的數據。接受帶有/不帶填充的標準或URL安全base64編碼。
int32,fixed32,uint32 string 1, -10, 0 JSON值將是十進制數。接受數字或字符串。
int64,fixed64,uint64 string "1", "-10" JSON值將是十進制字符串。接受數字或字符串。
float,double number 1.1, -10.0, 0, "NaN","Infinity" JSON值將是一個數字或一個特殊字符串值「NaN」,「Infinity」和「-Infinity」。接受數字或字符串。指數表示法也被接受。
any object {"@type": "url", "f": v, … } 若是Any包含具備特殊JSON映射的值,則將按以下方式進行轉換:。不然,該值將轉換爲JSON對象,並將插入該字段以指示實際的數據類型。{"@type": xxx, "value": yyy}``"@type"
Timestamp string "1972-01-01T10:00:20.021Z" 使用RFC 3339,其中生成的輸出將始終被Z標準化並使用0,3,6或9個小數位。也接受「Z」之外的偏移。
Duration string "1.000340012s", "1s" 生成的輸出始終包含0,3,6或9個小數位,具體取決於所需的精度,後跟後綴「s」。接受的是任何小數位(也沒有),只要它們符合納秒精度而且後綴「s」是必需的。
Struct object { … } 任何JSON對象。見。struct.proto
Wrapper types various types 2, "2", "foo", true,"true", null, 0, … 包裝器在JSON中使用與包裝基元類型相同的表示形式,除了null在數據轉換和傳輸期間容許和保留的表示形式。
FieldMask string "f.fooBar,h" 見。field_mask.proto
ListValue array [foo, bar, …]  
Value value   任何JSON值
NullValue null   JSON null

JSON選項

proto3 JSON實現能夠提供如下選項:

  • 使用默認值發出字段:默認狀況下,proto3 JSON輸出中省略了具備默認值的字段。實現能夠提供覆蓋此行爲的選項,並使用其默認值輸出字段。

  • 忽略未知字段:默認狀況下,Proto3 JSON解析器應拒絕未知字段,但能夠提供忽略解析中未知字段的選項。

  • 使用proto字段名稱而不是小寫駝峯名稱:默認狀況下,proto3 JSON打印機應將字段名稱轉換爲小寫駝峯並將其用做JSON名稱。實現能夠提供使用proto字段名稱做爲JSON名稱的選項。Proto3 JSON解析器須要接受轉換後的小寫駝峯名稱和proto字段名稱。

  • 將枚舉值發送爲整數而不是字符串:默認狀況下,在JSON輸出中使用枚舉值的名稱。能夠提供選項以使用枚舉值的數值。

選項

.proto文件中的各個聲明可使用許多選項進行註釋。選項不會更改聲明的總體含義,但可能會影響在特定上下文中處理它的方式。可用選項的完整列表在中定義google/protobuf/descriptor.proto

一些選項是文件級選項,這意味着它們應該在頂級範圍內編寫,而不是在任何消息,枚舉或服務定義中。一些選項是消息級選項,這意味着它們應該寫在消息定義中。一些選項是字段級選項,這意味着它們應該寫在字段定義中。選項也能夠寫在枚舉類型,枚舉值,服務類型和服務方法上; 可是,目前沒有任何有用的選擇。

如下是一些最經常使用的選項:

  • java_package(文件選項):用於生成的Java類的包。若是.proto文件中沒有給出顯式選項java_package,則默認狀況下將使用proto包(使用文件中的「package」關鍵字指定 .proto )。可是,proto包一般不能生成好的Java包,由於proto包不會以反向域名開頭。若是不生成Java代碼,則此選項無效。

    option java_package =「com.example.foo」;
  • java_multiple_files (文件選項):致使在包級別定義頂級消息,枚舉和服務,而不是在.proto文件以後命名的外部類中。

option java_multiple_files = true;
  • java_outer_classname(file option):要生成的最外層Java類(以及文件名)的類名。若是 .proto文件中沒有指定 java_outer_classname,則經過將.proto文件名轉換爲駝峯格式(所以 foo_bar.proto 成爲FooBar.java)來構造類名。若是不生成Java代碼,則此選項無效。

 option java_outer_classname =「Ponycopter」;

 

  • optimize_for

    (文件選項):能夠設置爲SPEEDCODE_SIZELITE_RUNTIME。這會如下列方式影響C ++和Java代碼生成器(可能還有第三方生成器):

    • SPEED(默認值):protobuf 編譯器將生成用於對消息類型進行序列化,解析和執行其餘常見操做的代碼。此代碼通過高度優化。

    • CODE_SIZE:protobuf 編譯器將生成最少的類,並依賴於基於反射的共享代碼來實現序列化,解析和各類其餘操做。所以生成的代碼比使用SPEED小得多,但操做會更慢。類仍將實現與SPEED模式徹底相同的公共API 。此模式在包含很是大數量的.proto文件的應用程序中最有用,而且不須要全部文件都很是快速。

    • LITE_RUNTIME:protobuf 編譯器將生成僅依賴於「lite」運行時庫(libprotobuf-lite而不是libprotobuf)的類。精簡版運行時比整個庫小得多(大約小一個數量級),但省略了描述符和反射等特定功能。這對於在移動電話等受限平臺上運行的應用程序尤爲有用。編譯器仍然會像在SPEED模式中同樣生成全部方法的快速實現。生成的類將僅實現MessageLite每種語言的接口,該接口僅提供完整Message接口的方法的子集。

    option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件選項):爲C ++生成的代碼啓用競技場分配

  • objc_class_prefix(文件選項):設置Objective-C類前綴,該前綴預先添加到此.proto的全部Objective-C生成的類和枚舉中。沒有默認值。您應該使用Apple建議的 3-5個大寫字符之間的前綴。請注意,Apple保留全部2個字母的前綴。

  • deprecated(字段選項):若是設置爲true,則表示該字段已棄用,新代碼不該使用該字段。在大多數語言中,這沒有實際效果。在Java中,這成爲一個@Deprecated註釋。未來,其餘特定於語言的代碼生成器可能會在字段的訪問器上生成棄用註釋,這將致使在編譯嘗試使用該字段的代碼時發出警告。若是任何人都沒有使用該字段,而且您但願阻止新用戶使用該字段,請考慮使用保留語句替換字段聲明。

    int32 old_field = 6 [deprecated = true];

自定義選項

Protocol Buffers還容許您定義和使用本身的選項。這是大多數人不須要的高級功能。若是您確實認爲須要建立本身的選項,請參閱Proto2語言指南以獲取詳細信息。請注意,建立自定義選項使用的擴展名僅容許用於proto3中的自定義選項。

生成您的類

根據實際工做須要,生成如下對應語言的自定義消息類型Java,Python,C ++,Go, Ruby, Objective-C,或C#的.proto文件,你須要運行protobuf 編譯器protoc.proto。若是還沒有安裝編譯器,請下載該軟件包並按照自述文件中的說明進行操做。對於Go,您還須要爲編譯器安裝一個特殊的代碼生成器插件:您能夠在GitHub上的golang / protobuf存儲庫中找到這個和安裝說明。

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 --objc_out = DST_DIR --csharp_out = DST_DIR  path / to / file .proto
  • IMPORT_PATH指定.proto解析import指令時在其中查找文件的目錄。若是省略,則使用當前目錄。能夠經過--proto_path屢次傳遞選項來指定多個導入目錄; 他們將按順序搜索。 能夠用做簡短的形式。 -I=*IMPORT_PATH*``--proto_path

  • 您能夠提供一個或多個輸出指令:

    爲了方便起見,若是DST_DIR結束於.zip或.jar,編譯器會將輸出寫入具備給定名稱的單個ZIP格式存檔文件。.jar輸出還將根據Java JAR規範的要求提供清單文件。請注意,若是輸出存檔已存在,則會被覆蓋; 編譯器不夠智能,沒法將文件添加到現有存檔中。

  • 您必須提供一個或多個.proto文件做爲輸入。.proto能夠一次指定多個文件。雖然文件是相對於當前目錄命名的,但每一個文件必須位於其中一個文件中,IMPORT_PATH以便編譯器能夠肯定其規範名稱。

     

其它

[Protocol Buffers 官網​]  https://developers.google.com/protocol-buffers/docs/proto3 Language Guide(proto3)

關於Protobuf 語言指南(proto3)詳解到這裏就結束了。

對了,也有使用protobuf整合的Netty項目工程地址: https://github.com/sanshengshui/netty-learning-example/tree/master/netty-springboot-protobuf

原創不易,若是感受不錯,但願給個推薦!您的支持是我寫做的最大動力!

版權聲明: 做者:穆書偉 博客園出處:https://www.cnblogs.com/sanshengshui github出處:https://github.com/sanshengshui     我的博客出處:https://sanshengshui.github.io/

相關文章
相關標籤/搜索