protocol buffer沒那麼難,不信你看這篇
簡介
上一篇文章我們對google的protobuf已經有了一個基本的認識,並且能夠使用相應的工具生成對應的程式碼了。但是對於.proto檔案的格式和具體支援的型別還不是很清楚。今天本文將會帶大家一探究竟。
注意,本文介紹的協議是proto3版本的。
定義一個訊息
protobuf中的主體被稱為是message,可以將其看做是我們在程式中定義的類。我們可以在.proto檔案中定義這個message物件,並且為其新增屬性,如下所示:
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
上例的第一行指定了.proto檔案的協議型別,這裡使用的是proto3,也是最新版的協議,如果不指定,預設情況下是proto2。
型別定義
這裡我們為SearchRequest物件,定義了三個屬性,其型別分別是String和int32。
String和int32都是簡單型別,protobuf支援的簡單型別如下:
protobuf型別 | 說明 | 對應的java型別 |
---|---|---|
double | 雙精度浮點型別 | double |
float | 浮點型別 | float |
int32 | 整型數字,最好不表示負數 | int |
int64 | 整型數字,最好不表示負數 | long |
uint32 | 無符號整數 | int |
uint64 | 無符號整數 | long |
sint32 | 帶符號整數 | int |
sint64 | 帶符號整數 | long |
fixed32 | 四個位元組的整數 | int |
fixed64 | 8個位元組的整數 | long |
sfixed32 | 4個位元組的帶符號整數 | int |
sfixed64 | 8個位元組的帶符號整數 | long |
bool | 布林型別 | boolean |
string | 字串 | String |
bytes | 位元組 | ByteString |
當然protobuf還支援複雜的組合型別和列舉型別。
列舉型別在protobuf中用enum來表示,我們來看一個列舉型別的定義:
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開始的,0也是列舉型別的預設值。
在列舉中,還可以定義具有相同value的列舉型別,但是這樣需要加上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.
}
}
在列舉型別中,如果我們後續對某些列舉型別進行了刪除,那麼被刪除的值可能會被後續的使用者使用,這樣就會造成潛在的程式碼隱患,為了解決這個問題,列舉提供了一個reserved的關鍵詞,被這個關鍵詞宣告的列舉型別,就不會被後續使用,如下所示:
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
reserved關鍵字也可以用在message的欄位中,表示後續不要使用到這些欄位,如下:
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
欄位的值
我們可以看到,每個message的欄位都分配了一個值,每個欄位的值在message中都是唯一的,這些值是用來定位在二進位制訊息格式中的欄位位置。所以一旦定義之後,不要隨意修改。
要注意的是值1-15在二進位制中使用的1個位元組來表示的,值16-2047需要使用2個位元組來表示,所以通常將1-15使用在最常見的欄位和可能重複的欄位,這樣可以節約編碼後的空間。
最小的值是1,最大的值是2的29次方-1,或者536,870,911。這中間從19000-19999是保留數字,不能使用。
當訊息被編譯之後,各個欄位將會被轉成為對應的型別,並且各個欄位型別將會被賦予不同的初始值。
strings的預設值是空字串,bytes的預設值是空bytes,bools的預設值是false,數字型別的預設值是0,列舉型別的預設值是列舉的第一個元素。
欄位描述符
每個訊息的欄位都可以有兩種描述符,第一種叫做singular,表示message中可以有0個或者1個這個欄位,這是proto3中預設的定義方式。
第二種叫做repeated,表示這個欄位在message中是可以重複的,也就是說它代表的是一個集合。
添加註釋
在proto中的註釋和C++的風格類似,可以使用: // 或者 /* ... */ 的風格來註釋,如下所示:
/* 這是一個註釋. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // 頁面的number
int32 result_per_page = 3; // 每頁的結果
}
巢狀型別
在一個message中還可以嵌入一個message,如下所示:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
在上例中,我們在SearchResponse定義了一個Result型別,在java中,實際上可以將其看做是巢狀類。
如果希望在message的定義類之外使用這個內部的message,則可以通過_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;
}
}
}
Map
如果想要在proto中定義map,可以這樣寫:
map<key_type, value_type> map_field = N;
這裡的value_type可以是除map之外的任意型別。注意map不能是repeated。
map中的資料的順序是不定的,我們不能依賴存入的map順序來判斷其取出的順序。
總結
以上就是proto3中定義宣告檔案該注意的事項了,大家在使用protobuf的時候要多加註意。
本文已收錄於 http://www.flydean.com/02-protocolbuf-detail/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!