gRPC-Protocol語法指南
阿新 • • 發佈:2020-09-28
本指南介紹瞭如何使用協議緩衝區語言來構造協議緩衝區資料(包括.proto檔案語法)以及如何從.proto檔案生成資料訪問類。 它涵蓋了協議緩衝區語言的proto3版本:有關proto2語法的資訊,請參見《[Proto2語言指南](https://developers.google.com/protocol-buffers/docs/proto)》。
這是參考指南–有關使用本文件中描述的許多功能的分步示例,請參見所選擇語言的[教程](https://developers.google.com/protocol-buffers/docs/tutorials)(當前僅適用於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檔案中指定的型別,以及自動生成的類中的相應型別:
![](https://img2020.cnblogs.com/blog/1145796/202009/1145796-20200928104957293-597820192.png)
在協議緩衝區編碼中序列化訊息時,您可以找到更多有關這些型別如何編碼的資訊。
[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