gRPC-Protocol語法指南

ddockerman發表於2020-09-28

本指南介紹瞭如何使用協議緩衝區語言來構造協議緩衝區資料(包括.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之一中,以便編譯器可以確定其規範名稱。

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