天天看點

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 雙精度浮點類型
float 浮點類型
int32 整型數字,最好不表示負數 int
int64 整型數字,最好不表示負數 long
uint32 無符号整數
uint64
sint32 帶符号整數
sint64
fixed32 四個位元組的整數
fixed64 8個位元組的整數
sfixed32 4個位元組的帶符号整數
sfixed64 8個位元組的帶符号整數
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的時候要多加注意。