Protobuf 語言指南(proto3)


Protobuf 語言指南(proto3)

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

定義消息類型

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

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。這必須是文件的第一個非空的非注釋行。

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

指定字段類型

在上面的示例中,所有字段都是標量類型:兩個整數(page_numberresult_per_page)和一個字符串(query)。但是,您還可以為字段指定合成類型,包括枚舉和其他消息類型。

分配標識號

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

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

指定字段規則

消息字段可以是以下之一:

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

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

在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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM