gRPC proto3語法指南
本文將描述如何使用protocol buffer 語言構造你的protocol buffer資料,包括.proto檔案語法規則,以及如何由.proto檔案生成資料訪問類。本文涵蓋了proto3版本的協議語言:想了解proto2的語法,檢視 Proto2 語言指南.
這是一個參考指南——給出了一步一步的操作的示例,示例使用了本文中描述的很多特性,這裡有適合你所選語言的教程:
定義一個訊息型別
首先讓我們來看一個非常簡單的例子。假定我們有這樣的需求,我們要定義一個搜尋請求訊息,每個訊息都包含一個查詢字串,和你感興趣的特定頁面編號,以及每個頁面的命中個數。
syntax = "proto3" ;
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 檔案的第一行指定了你使用的是proto3的語法:如果你不指定,protocol buffer 編譯器就會認為你使用的是proto2的語法。這個語句必須出現在.proto檔案的非空非註釋的第一行。
- 我們看到,搜尋請求訊息結構中定義指定了三個欄位(name/value pairs)。每個欄位都有一個名稱和型別。
指定欄位型別
在上述例子中,所有的欄位都是值型別:兩個整形(page_number and result_per_page) 和一個字串 (query)。然而,你也可以指定你的欄位的組合型別,包括列舉和其他訊息型別。
分配標識——tag
如你所見,訊息中的每個欄位都有一個唯一的數字標識。這些標識用來在訊息的二進位制格式中識別你的欄位,並且,一旦你的訊息投入使用,這些標識就不應該再被修改。
注意,標識是由1到15使用一個位元組來編碼,包括標識數字和欄位型別(你可以在Protocol Buffer 編碼中檢視更多詳細)。
標識16到2047佔用兩個位元組。所以你應該保留1到15,用作出現最頻繁的訊息型別的標識。記得為將來會繼續增加並可能頻繁出現的元素留一點兒標識區間,也就是說,不要一下子把1—15全部用完,為將來留一點兒哦。
你可以指定的最小的標識數字是1,最大是228,或者536,870,911。你也不能使用19000 到 19999之間的數字(FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber),因為它們被Protocol Buffers保留使用——如果你在自己的.proto檔案中使用了一個保留數字,protocol buffer 編譯器將會提示。同樣的,你不能使用任何之前保留的標識。
指定欄位規則
訊息欄位可以是下邊中的一種:
singular(單個): 符合語法規則的訊息包含零個或者一個這樣的欄位(最多一個)。
repeated(重複): 一個欄位在合法的訊息中可以重複出現一定次數(包括零次)。重複出現的值的次序將被保留。在proto3中,重複出現的值型別欄位預設採用壓縮編碼。你可以在這裡找到更多關於壓縮編碼的東西: Protocol Buffer Encoding。
新增更多訊息型別
多個訊息型別可以定義在一個.proto檔案中。這個在你定義多個關聯的訊息的時候非常有用,——這樣,舉個例子吧,如果你想定義你的搜尋訊息型別的響應訊息格式,你可以在同一個.proto檔案中新增如下的內容:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加註釋
在你的 .proto 檔案中添加註釋, 使用C/C++-風格的 // 語法,像下邊這樣:
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.
}
保留欄位
如果你通過刪除整個欄位更新了訊息型別,或者將整個欄位其註釋掉,未來使用者在編寫新的型別的時候能夠複用這些註釋掉的標識數字。然而,這會引起一些嚴重的問題,如果他們後來載入了同一個.proto的舊版,包括資料損壞,安全隱私bug等等。一個確保這種問題不會發生的辦法是,保留你要刪除的欄位的標識。Protocol buffer 編譯器將會提示以後使用者使用這些保留的欄位標識。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意,你不要混淆同一個保留語句中的欄位名稱和標識。
你的.proto編譯之後會生成什麼?
當你執行對一個.proto檔案的編譯之後,編譯器會為你選擇的語言生成程式碼。你在檔案中描述的訊息型別,包括獲取和設定欄位的值,序列化你的訊息到一個輸出流,以及從一個輸入流中轉換出你的訊息。
- 對於C++,編譯器會為每個.proto檔案生成一個.h 和一個.cc的檔案,為每一個給出的訊息型別生成一個類。
- 對於Java,編譯器會生成一個java檔案,其中為每一個訊息型別生成一個類,還有特殊的用來建立這些訊息類例項的Builder類,
- Python有一點小不同——Python編譯器生成一個模組兒,其中為每一個訊息型別生成一個靜態的描述器,在執行時,和一個metaclass一起使用來建立必要的Python資料訪問類。
- 對於Go,編譯器為每個訊息型別生成一個.pb.go檔案。
……
值型別
值型別的訊息欄位可以是一下型別中的一種——這個表格展示了可以在.proto檔案中使用的型別,以及自動生成的相應語言的型別:
.proto Type | 說明 | C++ Type | Java Type | Python Type[2] | Go Type |
---|---|---|---|---|---|
double | double | double | float | float64 | |
float | float | float | float | float32 | |
int32 | 使用可變長度編碼。對負數進行編碼時比較低效 – 如果你的欄位要使用負數值,請使用sint32來代替。 | int32 | int | int | int |
int64 | 使用可變長度編碼。對負數進行編碼時比較低效 – 如果你的欄位要使用負數值,請使用sint64來代替。 | int64 | long | int/long[3] | int64 |
uint32 | 使用可變長度編碼 | uint32 | int[1] | int/long[3] | uint32 |
uint64 | 使用可變長度編碼 | uint64 | long[1] | int/long[3] | uint64 |