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/

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章