天天看點

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

首先,讓我們看一個非常簡單的示例。 假設您要定義一個搜尋請求消息格式,其中每個搜尋請求都有一個查詢字元串,您感興趣的特定結果頁面以及每頁結果數量。 這是用于定義消息類型的.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版本也即将推出)。

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

gRPC-Protocol文法指南

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

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

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

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

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

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

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

  • 對于字元串,預設值為空字元串。
  • 對于位元組,預設值為空位元組。
  • 對于布爾值,預設值為false。
  • 對于數字類型,預設值為零。
  • 對于枚舉,預設值為第一個定義的枚舉值,必須為0。
  • 對于消息字段,未設定該字段。它的确切值取決于語言。有關詳細資訊,請參見生成的代碼指南。
  • 重複字段的預設值為空(通常為相應語言的空清單)。

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

有關預設值在生成的代碼中如何工作的更多詳細資訊,請參見所選語言的生成的代碼指南。

在定義消息類型時,您可能希望其字段之一僅具有一個預定義的值清單之一。 例如,假設您要為每個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";
}
           

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

您可以使用其他消息類型作為字段類型。 例如,假設您要在每條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消息使用它們,也可以)。

您可以在其他消息類型中定義和使用消息類型,如以下示例所示–在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;
    }
  }
}
           

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

  • 不要更改任何現有字段的字段編号。
  • 如果添加新字段,則仍可以使用新生成的代碼來解析使用“舊”消息格式通過代碼序列化的任何消息。您應該記住這些元素的預設值,以便新代碼可以與舊代碼生成的消息正确互動。同樣,由新代碼建立的消息可以由舊代碼解析:舊的二進制檔案在解析時隻會忽略新字段。有關詳細資訊,請參見“未知字段”部分。
  • 隻要在更新的消息類型中不再使用字段号,就可以删除字段。您可能想要重命名該字段,或者添加字首“ 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的成員是安全且二進制相容的。如果您确定一次沒有代碼設定多個字段,那麼将多個字段移動到一個新字段中可能是安全的。将任何字段移至現有字段都不安全。

未知字段是格式正确的協定緩沖區序列化資料,表示解析器無法識别的字段。 例如,當舊的二進制檔案使用新字段解析新二進制檔案發送的資料時,這些新字段将成為舊二進制檔案中的未知字段。

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

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消息。

要在.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的成員,是以無法分辨出差異。

如果要在資料定義中建立關聯映射,則協定緩沖區提供了友善的快捷方式文法:

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;
           

任何支援映射的協定緩沖區實作都必須産生并接受上述定義可以接受的資料。

您可以在.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檔案來解析所有類型名稱。每種語言的代碼生成器都知道如何引用該語言中的每種類型,即使它具有不同的範圍規則。

如果要将消息類型與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頁面。

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

如果JSON編碼的資料中缺少某個值,或者該值為null,則在解析為協定緩沖區時,它将被解釋為适當的預設值。 如果字段在協定緩沖區中具有預設值,則預設情況下會在JSON編碼資料中将其省略以節省空間。 一個實作可以提供選項,以在JSON編碼的輸出中發出具有預設值的字段。

gRPC-Protocol文法指南

JSON options

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

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

.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中的自定義選項。

要生成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之一中,以便編譯器可以确定其規範名稱。

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

繼續閱讀