1. 程式人生 > 其它 >protocol buffer沒那麼難,不信你看這篇

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/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!