gRPC-Protocol語法指南

本指南介紹了如何使用協議緩衝區語言來構造協議緩衝區數據(包括.proto文件語法)以及如何從.proto文件生成數據訪問類。 它涵蓋了協議緩衝區語言的proto3版本:有關proto2語法的信息,請參見《Proto2語言指南》。
這是參考指南–有關使用本文檔中描述的許多功能的分步示例,請參見所選擇語言的教程(當前僅適用於proto2;即將推出更多proto3文檔)。

語法指南 (proto3)

  • Defining A Message Type
  • Scalar Value Types
  • Default Values
  • Enumerations
  • Using Other Message Types
  • Nested Types
  • Updating A Message Type
  • Unknown Fields
  • Any
  • Oneof
  • Maps
  • Packages
  • Defining Services
  • JSON Mapping
  • Options
  • Generating Your Classes

Defining A Message Type

首先,讓我們看一個非常簡單的示例。 假設您要定義一個搜索請求消息格式,其中每個搜索請求都有一個查詢字符串,您感興趣的特定結果頁面以及每頁結果數量。 這是用於定義消息類型的.proto文件。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定您使用的是proto3語法:如果不這樣做,則協議緩衝區編譯器將假定您使用的是proto2。 這必須是文件的第一行非空,非注釋行。
  • SearchRequest消息定義指定三個字段(名稱/值對),每個字段要包含在此類型的消息中,每個字段對應一個。 每個字段都有一個名稱和類型。

Specifying Field Types

在上面的示例中,所有字段均為標量類型:兩個整數(page_number和result_per_page)和一個字符串(查詢)。 但是,您也可以為字段指定複合類型,包括枚舉和其他消息類型。

Assigning Field Numbers

如您所見,消息定義中的每個字段都有一個唯一的編號。這些字段號用於標識消息二進制格式的字段,一旦使用了消息類型,就不應更改這些字段號。請注意,範圍為1到15的字段編號需要一個位元組來編碼,包括字段編號和字段的類型(您可以在協議緩衝區編碼中找到更多有關此內容的信息)。 16到2047之間的字段號佔用兩個位元組。因此,您應該為經常出現的消息元素保留數字1到15。切記為將來可能添加的頻繁出現的元素留出一些空間。

您可以指定的最小字段號是1,最大字段號是229-1或536,870,911。您也不能使用數字19000到19999(FieldDescriptor :: kFirstReservedNumber到FieldDescriptor :: kLastReservedNumber),因為它們是為協議緩衝區實現保留的-如果在.proto中使用這些保留數之一,協議緩衝區編譯器會抱怨。同樣,您不能使用任何以前保留的字段號。

Specifying Field Rules

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

  • 單數:格式正確的郵件可以包含零個或一個此字段(但不能超過一個)。 這是proto3語法的默認字段規則。
  • 重複:此字段可以在格式正確的消息中重複任意次(包括零次)。 重複值的順序將保留。

在proto3中,標量數字類型的重複字段默認情況下使用打包編碼。
您可以在協議緩衝區編碼中找到有關打包編碼的更多信息。

Adding More Message Types

可以在單個.proto文件中定義多種消息類型。 如果要定義多個相關消息,這很有用–例如,如果要定義與SearchResponse消息類型相對應的答覆消息格式,可以將其添加到相同的.proto中:

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

message SearchResponse {
 ...
}

Adding Comments

要將注釋添加到.proto文件,請使用C / C ++樣式//和/ * … * /語法。

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

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

Reserved Fields

如果您通過完全刪除字段或將其注釋掉來更新消息類型,則將來的用戶在自己對該類型進行更新時可以重用該字段號。 如果他們以後加載同一.proto的舊版本,可能會導致嚴重的問題,包括數據損壞,隱私錯誤等。 確保不會發生這種情況的一種方法是指定保留已刪除字段的字段編號(和/或名稱,這也可能導致JSON序列化問題)。 如果將來有任何用戶嘗試使用這些字段標識符,則協議緩衝區編譯器會抱怨。

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

請注意,您不能在同一保留語句中混用字段名稱和字段編號。

What’s Generated From Your .proto?

在.proto上運行協議緩衝區編譯器時,編譯器會以您選擇的語言生成代碼,您將需要使用該文件處理文件中描述的消息類型,包括獲取和設置字段值,將消息序列化為輸出流,並從輸入流中解析消息。

  • 對於C ++,編譯器從每個.proto生成.h和.cc文件,並為文件中描述的每種消息類型提供一個類。
  • 對於Java,編譯器會生成一個.java文件,其中包含每種消息類型的類以及用於創建消息類實例的特殊Builder類。
  • Python稍有不同-Python編譯器會在.proto中生成帶有每種消息類型的靜態描述符的模塊,然後將該模塊與元類一起使用,以在運行時創建必要的Python數據訪問類。
  • 對於Go,編譯器會生成一個.pb.go文件,其中包含文件中每種消息類型的類型。
  • 對於Ruby,編譯器將使用包含您的消息類型的Ruby模塊生成一個.rb文件。
  • 對於Objective-C,編譯器從每個.proto生成一個pbobjc.h和pbobjc.m文件,並為文件中描述的每種消息類型提供一個類。
  • 對於C#,編譯器從每個.proto生成一個.cs文件,並為文件中描述的每種消息類型提供一個類。
  • 對於Dart,編譯器會生成一個.pb.dart文件,其中包含文件中每種消息類型的類。

您可以按照所選語言的教程(即將推出proto3版本)查找有關每種語言使用API​​的更多信息。有關API的更多詳細信息,請參見相關的API參考(proto3版本也即將推出)。

Scalar Value Types

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

在協議緩衝區編碼中序列化消息時,您可以找到更多有關這些類型如何編碼的信息。

[1]在Java中,無符號的32位和64位整數使用帶符號的對等體表示,最高位僅存儲在符號位中。

[2]在所有情況下,將值設置為字段都會執行類型檢查以確保其有效。

[3] 64位或無符號32位整數在解碼時始終表示為long,但是如果在設置字段時給出了int,則可以為int。 在所有情況下,該值都必須適合設置時表示的類型。 參見[2]。

[4] Python字符串在解碼時表示為unicode,但如果給出了ASCII字符串,則可以為str(此字符串可能會發生變化)。

[5]在64位計算機上使用Integer,在32位計算機上使用string。

Default Values

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

  • 對於字符串,默認值為空字符串。
  • 對於位元組,默認值為空位元組。
  • 對於布爾值,默認值為false。
  • 對於數字類型,默認值為零。
  • 對於枚舉,默認值為第一個定義的枚舉值,必須為0。
  • 對於消息字段,未設置該字段。它的確切值取決於語言。有關詳細信息,請參見生成的代碼指南。
  • 重複字段的默認值為空(通常為相應語言的空列表)。

請注意,對於標量消息字段,一旦解析了一條消息,就無法判斷是將字段明確設置為默認值(例如,是否將布爾值設置為false)還是根本沒有設置:您應該在定義消息類型時要注意。例如,如果您不希望默認情況下也發生這種情況,則當布爾值設置為false時,沒有布爾值會打開某些行為。還要注意,如果將標量消息字段設置為其默認值,則該值將不會在線路上被序列化。

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

Enumerations

在定義消息類型時,您可能希望其字段之一僅具有一個預定義的值列表之一。 例如,假設您要為每個SearchRequest添加一個語料庫字段,該語料庫可以是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO。 您可以通過在消息定義中添加一個枚舉以及每個可能值的常量來非常簡單地完成此操作。

在下面的示例中,我們添加了一個名為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,否則協議編譯器將在找到別名時生成一條錯誤消息。

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}
message MyMessage2 {
  enum EnumNotAllowingAlias {
    UNKNOWN = 0;
    STARTED = 1;
    // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
  }
}

枚舉器常量必須在32位整數範圍內。 由於枚舉值在導線上使用varint編碼,因此負值效率不高,因此不建議使用。 您可以在消息定義內定義枚舉,如上例所示,也可以在外部定義-這些枚舉可以在.proto文件中的任何消息定義中重複使用。 您還可以使用語法_MessageType . EnumType_將一條消息中聲明的枚舉類型用作另一條消息中的字段類型。

在使用枚舉的.proto上運行協議緩衝區編譯器時,生成的代碼將具有一個對應的Java或C ++枚舉,一個特殊的Python EnumDescriptor類,用於在運行時創建帶有整數值的符號常量集 生成的類。

反序列化期間,無法識別的枚舉值將保留在消息中,儘管在反序列化消息時如何表示該值取決於語言。 在支持具有超出指定符號範圍的值的開放式枚舉類型的語言(例如C ++和Go)中,未知的枚舉值僅存儲為其基礎整數表示形式。 在諸如Java之類的具有封閉枚舉類型的語言中,枚舉中的大小寫用於表示無法識別的值,並且可以使用特殊的訪問器訪問基礎整數。 無論哪種情況,如果消息被序列化,則無法識別的值仍將與消息一起序列化。

有關如何在應用程序中使用消息枚舉的更多信息,請參見針對所選語言的生成的代碼指南。

Reserved Values

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

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

請注意,您不能在同一保留語句中混合使用字段名和數字值。

Using Other Message Types

您可以使用其他消息類型作為字段類型。 例如,假設您要在每條SearchResponse消息中包括結果消息–為此,您可以在同一.proto中定義結果消息類型,然後在SearchResponse中指定結果類型的字段:

message SearchResponse {
  repeated Result results = 1;
}

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

Importing Definitions

在上面的示例中,「結果」消息類型與SearchResponse定義在同一文件中-如果要在另一個.proto文件中定義要用作字段類型的消息類型,該怎麼辦?

您可以通過導入其他.proto文件使用它們的定義。 要導入另一個.proto的定義,請在文件頂部添加一個import語句:

import "myproject/other_protos.proto";

默認情況下,您只能使用直接導入的.proto文件中的定義。 但是,有時您可能需要將.proto文件移動到新位置。 現在,您可以直接在原始位置放置一個虛擬.proto文件,而不是直接移動.proto文件並一次更改所有呼叫站點,而是使用import public概念將所有導入轉發到新位置。 任何導入包含導入公共聲明的原型的人都可以可傳遞地依賴導入公共依賴項。 例如:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

協議編譯器使用-I /-proto_path標誌在協議編譯器命令行中指定的一組目錄中搜索導入的文件。 如果未給出標誌,它將在調用編譯器的目錄中查找。 通常,應將–proto_path標誌設置為項目的根目錄,並對所有導入使用完全限定的名稱。

Using proto2 Message Types

可以導入proto2消息類型並在proto3消息中使用它們,反之亦然。 但是,不能在proto3語法中直接使用proto2枚舉(如果導入的proto2消息使用它們,也可以)。

Nested Types

您可以在其他消息類型中定義和使用消息類型,如以下示例所示–在SearchResponse消息中定義了Result消息:

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

Updating A Message Type

如果現有消息類型不再滿足您的所有需求(例如,您希望消息格式具有一個額外的字段),但是您仍然希望使用以舊格式創建的代碼,請不要擔心!在不破壞任何現有代碼的情況下更新消息類型非常簡單。只要記住以下規則:

  • 不要更改任何現有字段的字段編號。
  • 如果添加新字段,則仍可以使用新生成的代碼來解析使用「舊」消息格式通過代碼序列化的任何消息。您應該記住這些元素的默認值,以便新代碼可以與舊代碼生成的消息正確交互。同樣,由新代碼創建的消息可以由舊代碼解析:舊的二進制文件在解析時只會忽略新字段。有關詳細信息,請參見「未知字段」部分。
  • 只要在更新的消息類型中不再使用字段號,就可以刪除字段。您可能想要重命名該字段,或者添加前綴「 OBSOLETE_」,或者保留該字段編號,以使.proto的將來用戶不會意外重用該編號。
  • int32,uint32,int64,uint64和bool都是兼容的–這意味着您可以將字段從這些類型中的一種更改為另一種,而不會破壞向前或向後的兼容性。如果從對應的類型不適合的導線中解析出一個數字,則將獲得與在C ++中將數字強制轉換為該類型一樣的效果(例如,如果將64位數字讀取為int32,它將被截斷為32位)。
  • sint32和sint64彼此兼容,但與其他整數類型不兼容。
  • 字符串和位元組兼容,只要位元組是有效的UTF-8。
  • 如果位元組包含消息的編碼版本,則嵌入式消息與位元組兼容。
  • fixed32與sfixed32兼容,fixed64與sfixed64兼容。
  • 對於字符串,位元組和消息字段,可選與重複兼容。給定重複字段的序列化數據作為輸入,如果期望該字段是可選的,則如果它是原始類型字段,則將採用最後一個輸入值;如果是消息類型字段,則將合併所有輸入元素。請注意,這對於數字類型(包括布爾值和枚舉)通常並不安全。重複的數字類型字段可以以打包格式序列化,當期望使用可選字段時,該格式將無法正確解析。
  • 在有線格式方面,enum與int32,uint32,int64和uint64兼容(請注意,如果值不合適,該值將被截斷)。但是請注意,客戶端代碼在反序列化消息時可能會以不同的方式對待它們:例如,無法識別的proto3枚舉類型將保留在消息中,但是反序列化消息時如何表示這取決於語言。 Int字段始終只是保留其值。
  • 將單個值更改為新的oneof的成員是安全且二進制兼容的。如果您確定一次沒有代碼設置多個字段,那麼將多個字段移動到一個新字段中可能是安全的。將任何字段移至現有字段都不安全。

Unknown Fields

未知字段是格式正確的協議緩衝區序列化數據,表示解析器無法識別的字段。 例如,當舊的二進制文件使用新字段解析新二進制文件發送的數據時,這些新字段將成為舊二進制文件中的未知字段。

最初,proto3消息在解析過程中總是丟棄未知字段,但是在版本3.5中,我們重新引入了保留未知字段以匹配proto2行為的功能。 在版本3.5和更高版本中,未知字段將在解析期間保留並包含在序列化輸出中。

Any

Any消息類型使您可以將消息用作嵌入式類型,而無需定義它們的.proto。 Any包含任意序列化的消息(以位元組為單位)以及URL,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

不同的語言實現將支持運行時庫幫助程序以類型安全的方式打包和解壓縮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 ...
  }
}

當前,正在開發用於任何類型的運行時庫。

如果您已經熟悉proto2語法,則Any可以保存任意proto3消息,類似於可以允許擴展的proto2消息。

Oneof

要在.proto中定義oneof,請使用oneof關鍵字,後跟您的oneof名稱,在本例中為test_oneof:

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

然後,將oneof字段添加到oneof定義。 您可以添加任何類型的字段,但地圖字段和重複字段除外。

在生成的代碼中,oneof字段具有與常規字段相同的getter和setter。 您還將獲得一種特殊的方法來檢查oneof中的哪個值(如果有)。 您可以在相關的API參考中找到有關所選語言的oneof API的更多信息。

Oneof Features

  • 設置oneof字段將自動清除oneof的所有其他成員。 因此,如果您設置了多個字段,則只有您設置的最後一個字段仍具有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
  • 如果解析器在線路上遇到同一個對象的多個成員,則在解析的消息中僅使用最後看到的成員。

  • 一個不能重複。

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

  • 如果將oneof字段設置為默認值(例如將int32 oneof字段設置為0),則將設置該oneof字段的「大小寫」,並且該值將在線路上序列化。

  • 如果您使用的是C++,請確保您的代碼不會導致內存崩潰。 以下示例代碼將崩潰,因為通過調用set_name()方法已經刪除了sub_message。

SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here
  • 同樣,在C ++中,如果您用aofs交換(兩條)消息,則每條消息都將以另一種形式的oneof結尾:在下面的示例中,msg1將具有sub_message,而msg2將具有名稱。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

Backwards-compatibility issues

添加或刪除字段之一時請多加註意。 如果檢查oneof的值返回None / NOT_SET,則可能表示尚未設置oneof或已將其設置為oneof的不同版本中的字段。 由於無法知道導線上的未知字段是否是oneof的成員,因此無法分辨出差異。

Maps

如果要在數據定義中創建關聯映射,則協議緩衝區提供了方便的快捷方式語法:

map<key_type, value_type> map_field = N;

…其中key_type可以是任何整數或字符串類型(因此,浮點類型和位元組除外的任何標量類型)。 請注意,枚舉不是有效的key_type。 value_type可以是除另一個映射以外的任何類型。

因此,例如,如果您想創建一個項目地圖,其中每個Project消息都與一個字符串鍵相關聯,則可以這樣定義它:

map<string, Project> projects = 3;

  • 映射字段不能重複。
  • 地圖值的線格式排序和地圖迭代排序是不確定的,因此您不能依賴於地圖項的特定順序。
  • 為.proto生成文本格式時,地圖按鍵排序。 數字鍵按數字排序。
  • 從導線解析或合併時,如果存在重複的映射鍵,則使用最後看到的鍵。 從文本格式解析地圖時,如果鍵重複,則解析可能會失敗。
  • 如果為映射字段提供鍵但沒有值,則序列化字段時的行為取決於語言。 在C ++,Java和Python中,類型的默認值是序列化的,而在其他語言中,則沒有序列化的值。

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

Backwards compatibility

映射語法與網上的以下語法等效,因此不支持映射的協議緩衝區實現仍可以處理您的數據:

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

repeated MapFieldEntry map_field = N;

任何支持映射的協議緩衝區實現都必須產生並接受上述定義可以接受的數據。

Packages

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

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

然後,您可以在定義消息類型的字段時使用包說明符:

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

包說明符影響生成的代碼的方式取決於您選擇的語言:

  • 在C ++中,生成的類包裝在C ++名稱空間中。例如,Open將位於名稱空間foo :: bar中。
  • 在Java中,除非您在.proto文件中明確提供選項java_package,否則該包將用作Java包。
  • 在Python中,package指令將被忽略,因為Python模塊是根據其在文件系統中的位置進行組織的。
  • 在Go中,除非您在.proto文件中明確提供了go_package選項,否則該包將用作Go包名稱。
  • 在Ruby中,生成的類被包裝在嵌套的Ruby名稱空間中,轉換為所需的Ruby大寫樣式(首字母大寫;如果首字符不是字母,則以PB_開頭)。例如,Open將位於命名空間Foo :: Bar中。
  • 在C#中,除非轉換為.proto文件中明確提供選項csharp_namespace,否則在轉換為PascalCase之後,該程序包將用作命名空間。例如,Open將位於命名空間Foo.Bar中。

軟件包和名稱解析

協議緩衝語言中的類型名稱解析類似於C ++:首先搜索最裏面的作用域,然後搜索最裏面的作用域,依此類推,每個包都被視為其父包「內部」。領先的「。」 (例如.foo.bar.Baz)表示從最外面的範圍開始。

協議緩衝區編譯器通過解析導入的.proto文件來解析所有類型名稱。每種語言的代碼生成器都知道如何引用該語言中的每種類型,即使它具有不同的範圍規則。

Defining Services

如果要將消息類型與RPC(遠程過程調用)系統一起使用,則可以在.proto文件中定義RPC服務接口,並且協議緩衝區編譯器將以您選擇的語言生成服務接口代碼和存根。 因此,例如,如果要使用接收SearchRequest並返回SearchResponse的方法來定義RPC服務,則可以在.proto文件中對其進行定義,如下所示:

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

與協議緩衝區一起使用的最直接的RPC系統是gRPC:這是Google開發的與語言和平台無關的開源RPC系統。 gRPC與協議緩衝區配合使用特別好,並允許您使用特殊的協議緩衝區編譯器插件直接從.proto文件生成相關的RPC代碼。

如果您不想使用gRPC,也可以在自己的RPC實現中使用協議緩衝區。 您可以在《 Proto2語言指南》中找到有關此內容的更多信息。

還有許多正在進行的第三方項目正在為協議緩衝區開發RPC實現。 有關我們知道的項目的鏈接列表,請參見第三方加載項Wiki頁面。

JSON Mapping

Proto3支持JSON中的規範編碼,從而使在系統之間共享數據更加容易。 下表按類型對編碼進行了描述。

如果JSON編碼的數據中缺少某個值,或者該值為null,則在解析為協議緩衝區時,它將被解釋為適當的默認值。 如果字段在協議緩衝區中具有默認值,則默認情況下會在JSON編碼數據中將其省略以節省空間。 一個實現可以提供選項,以在JSON編碼的輸出中發出具有默認值的字段。

JSON options

一個proto3 JSON實現可以提供以下選項:

  • 發出具有默認值的字段:默認情況下,proto3 JSON輸出中省略具有默認值的字段。 一個實現可以提供一個選項,以使用其默認值覆蓋此行為和輸出字段。
  • 忽略未知字段:默認情況下,Proto3 JSON解析器應拒絕未知字段,但可以提供在解析時忽略未知字段的選項。
  • 使用proto字段名稱代替lowerCamelCase名稱:默認情況下,proto3 JSON打印機應將字段名稱轉換為lowerCamelCase並將其用作JSON名稱。 一個實現可以提供一個選項,改為使用原型字段名稱作為JSON名稱。 Proto3 JSON解析器必須接受轉換後的lowerCamelCase名稱和原型字段名稱。
  • 將枚舉值作為整數而不是字符串發送:枚舉值的名稱在JSON輸出中默認使用。 可以提供一個選項來代替使用枚舉值的數字值。

Options

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

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

以下是一些最常用的選項:

  • java_package(文件選項):要用於生成的Java類的包。 如果.proto文件中未提供顯式的java_package選項,則默認情況下將使用proto軟件包(在.proto文件中使用「 package」關鍵字指定)。 但是,proto軟件包通常不能作為Java包,因為proto軟件包不應以反向域名開頭。 如果未生成Java代碼,則此選項無效。
option java_package = "com.example.foo";
  • java_multiple_files(文件選項):使頂級消息,枚舉和服務在程序包級別定義,而不是在以.proto文件命名的外部類內部定義。
option java_multiple_files = true;
  • java_outer_classname(文件選項):您要生成的最外層Java類的類名(以及文件名)。 如果在.proto文件中未指定顯式的java_outer_classname,則通過將.proto文件名轉換為駝峰式大小寫來構造類名(因此foo_bar.proto變為FooBar.java)。 如果未生成Java代碼,則此選項無效。
option java_outer_classname = "Ponycopter";
  • optimize_for(文件選項):可以設置為SPEED,CODE_SIZE或LITE_RUNTIME。 這會通過以下方式影響C ++和Java代碼生成器(可能還有第三方生成器):
    [1] SPEED(默認):協議緩衝區編譯器將生成代碼,用於對消息類型進行序列化,解析和執行其他常見操作。此代碼已高度優化。
    [2] CODE_SIZE:協議緩衝區編譯器將生成最少的類,並將依賴於基於反射的共享代碼來實現序列化,解析和其他各種操作。因此,生成的代碼將比使用SPEED的代碼小得多,但是操作會更慢。類仍將實現與在SPEED模式下完全相同的公共API。此模式在包含大量.proto文件且不需要所有文件都快速達到要求的應用程序中最有用。
    [3] LITE_RUNTIME:協議緩衝區編譯器將生成僅依賴於「 lite」運行時庫的類(libprotobuf-lite而非libprotobuf)。精簡版運行時比完整庫要小得多(大約小一個數量級),但省略了某些功能,例如描述符和反射。這對於在受限平台(例如手機)上運行的應用程序特別有用。編譯器仍將像在SPEED模式下一樣快速生成所有方法的實現。生成的類將僅以每種語言實現MessageLite接口,該接口僅提供完整Message接口方法的子集。
option optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件選項):啟用C ++生成代碼的舞台分配。

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

  • 不推薦使用(字段選項):如果設置為true,則表示不推薦使用該字段,新代碼不應使用該字段。 在大多數語言中,這沒有實際效果。 在Java中,這成為@Deprecated注釋。 將來,其他特定於語言的代碼生成器可能會在該字段的訪問器上生成棄用注釋,這反過來將導致在編譯嘗試使用該字段的代碼時發出警告。 如果該字段未被任何人使用,並且您想阻止新用戶使用它,請考慮使用保留語句替換字段聲明。

int32 old_field = 6 [deprecated = true];

Custom Options

協議緩衝區還允許您定義和使用自己的選項。 這是大多數人不需要的高級功能。 如果您確實需要創建自己的選項,請參閱《 Proto2語言指南》以了解詳細信息。 請注意,創建自定義選項使用擴展名,擴展名僅適用於proto3中的自定義選項。

Generating Your Classes

要生成Java,Python,C ++,Go,Ruby,Objective-C或C#代碼,您需要使用.proto文件中定義的消息類型,您需要在.proto上運行協議緩衝區編譯器協議。 如果尚未安裝編譯器,請下載軟件包並按照自述文件中的說明進行操作。 對於Go,您還需要為編譯器安裝一個特殊的代碼生成器插件:您可以在GitHub上的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 --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH指定解析導入指令時在其中查找.proto文件的目錄。 如果省略,則使用當前目錄。 可以通過多次傳遞–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中生成Ruby代碼。 Ruby生成的代碼參考即將推出!
    –objc_out在DST_DIR中生成Objective-C代碼。有關更多信息,請參見Objective-C生成的代碼參考。
    –csharp_out在DST_DIR中生成C#代碼。有關更多信息,請參見C#生成的代碼參考。
    –php_out在DST_DIR中生成PHP代碼。欲了解更多便利,請參見PHP生成的代碼參考。如果DST_DIR以.zip或.jar結尾,則編譯器會將輸出寫入給定名稱的單個ZIP格式存檔文件。根據Java JAR規範的要求,還將為.jar輸出提供清單文件。注意,如果輸出存檔已經存在,它將被覆蓋;編譯器不夠 智能,無法將文件添加到現有存檔中。

  • 您必須提供一個或多個.proto文件作為輸入。 可以一次指定多個.proto文件。 儘管這些文件是相對於當前目錄命名的,但是每個文件都必須位於IMPORT_PATH之一中,以便編譯器可以確定其規範名稱。

參考文檔://developers.google.com/protocol-buffers/docs/proto3