gRPC之proto語法

MichaelCc發表於2018-12-13

本文描述如何使用proto3語法去構造你的資料結構,對官方文件不完全譯文,只是摘出本人需要的部分來簡單翻譯官網地址,如果你無法進入官網連結請自行"跳牆"-_-.

目錄
  • 1 定義訊息型別
    • 1.1 指定欄位型別
    • 1.2 分配標量
    • 1.3 指定屬性規則
    • 1.4 新增更多的訊息型別
    • 1.5 新增註釋
    • 1.6 保留屬性
  • 2 資料型別
  • 3 預設值
  • 4 列舉
  • 5 引用其他的訊息型別
    • 5.1 匯入其他proto中定義的訊息
  • 6 內嵌型別
  • 8 包
  • 9 服務定義
  • 10 選項

1.定義訊息型別

讓我們先看一個 proto3 的查詢請求引數的訊息格式的例子,這個請求引數例子模仿分頁查詢請求,他有一個請求引數字串,有一個當前頁的引數還有一個每頁返回資料大小的引數,proto檔案內容如下:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
複製程式碼
  • 第一行的含義是限定該檔案使用的是proto3的語法,如果沒有 syntax = "proto3";

  • SearchRequest定義有三個承載訊息的屬性,每一個被定義在SearchRequest訊息體中的欄位,都是由資料型別和屬性名稱組成。

1.1 指定欄位型別

在上面的例子中,所有的屬性都是標量,兩個整型(page_number、result_per_page)和一個字串(query),你還可以在指定複合型別,包括列舉型別或者其他的訊息型別。

1.2 分配標量

就像所看見的一樣,每一個被定義在訊息中的欄位都會被分配給一個唯一的標量,這些標量用於標識你定義在二進位制訊息格式中的屬性,標量一旦被定義就不允許在使用過程中再次被改變。標量的值在1~15的這個範圍裡佔一個位元組編碼(詳情請參看 谷歌的 Protocol Buffer Encoding )。

1.3 指定屬性規則

訊息屬性規則如下:

  • singular: 一個正確的訊息可以有零個或者多個這樣的訊息屬性(但是不要超過一個).
  • repeated: 這個屬性可以在一個正確的訊息格式中重複任意次數(包括零次),
    在proto3中,標量數字型別的重複欄位預設使用壓縮編碼

1.4 新增更多的訊息型別

在一個proto檔案中可以定義多個訊息型別,你可以在一個檔案中定義一些相關的訊息型別,上面的例子proto檔案中只有一個請求查詢的訊息型別,現在可以為他多新增一個響應的訊息型別,具體如下:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
    ....
}
複製程式碼

1.5 新增註釋

proto檔案中的註釋使用的是c/c++中的單行註釋 // 語法風格。
如下:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // 當前頁數
  int32 result_per_page = 3;  // 每頁資料返回的資料量
複製程式碼

1.6 保留屬性

為了避免在載入相同的.proto的舊版本,包括資料損壞,隱含的錯誤等,這可能會導致嚴重的問題的方法是指定刪除的欄位的欄位標籤(和/或名稱,也可能導致JSON序列化的問題)被保留。 如果將來的使用者嘗試使用這些欄位識別符號,協議緩衝區編譯器將會報錯。


保留欄位的使用例子:

message Foo {
  reserved 2;
  reserved "foo", "bar";
}
複製程式碼

上述例子定義保留屬性為"foo", "bar",定義保留屬性位置為2,即在2這個位置上不可以定義屬性,如:string name=2;是不允許的,編譯器在編譯proto檔案的時候如果發現,2這個位置上有屬性被定義則會報錯。

2 資料型別



一個資訊標量具有如下表格所示的資料型別,下表主要是對.proto檔案的值型別和java的值型別的對照表

.proto Type Java Type
double double
float float
int32 int
int64 long
uint32 int
uint64 long
sint32 int
sint64 long
fixed32 int
fixed64 long
sfixed32 int
sfixed64 long
bool boolean
string String
bytes ByteString

詳情參看官方文件

3 預設值

當proto訊息被解析成具體的語言的時候,如果訊息編碼沒包含特定的元素,則訊息物件中的屬性會被設定預設值,這些預設值具體如下:

  • string型別,預設值是空字串,注意不是null
  • bytes型別,預設值是空bytes
  • bool型別,預設值是false
  • 數字型別,預設值是0
  • 列舉型別,預設值是第一個列舉值,即0
  • repeated修飾的屬性,預設值是空(在相對應的程式語言中通常是一個空的list).

4 列舉

proto允許你在定義的訊息型別的時候定義列舉型別,如下例,在訊息型別中定義並使用列舉型別:

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,原因如下:

  • 必須有一個0作為值,以至於我們可是使用0作為預設值
  • 第一個元素的值取0,用於與第一個元素列舉值作為預設值的proto2語義相容


    列舉型別允許你定義別名,別名的作用是分配不中的標量,使用相同的常量值,使用別名只需要在定義列舉型別的第一行中新增allow_alias選項,並將值設定為true即可,如果沒有設定該值就是用別名,在編譯的時候會報錯。


    官網例子如下:
enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  //如果解除這個註釋編譯器在編譯該proto文的時候會報錯
  // RUNNING = 1;  
}
複製程式碼

proto支援的列舉值的範圍是32位的整形,即Java 中的int型別,其他請參看官網。

5 引用其他的訊息型別

你可以在定義訊息型別的時候飲用其他已經定義好的訊息型別作為新訊息型別的屬性,官網例子如下:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
複製程式碼

在上面的訊息例子中,SearchResponse這個響應訊息型別的屬性results,返回的是一個Result型別的訊息列表。

5.1 匯入其他proto中定義的訊息

在上面的例子中,Result和SearchResponse訊息型別被定義在同一個.proto檔案中,如果把他們分成兩個檔案定義,應該如何引用呢?


proto中為我們提供了import 關鍵字用於引入不同.proto檔案中的訊息型別,你可以在你的.proto檔案的頂部加入如下語句因為其他.proto檔案的訊息型別:
import "myproject/other_protos.proto";
例子:

  • 檔名稱search_response.proto
syntax = "proto3";
import "test/result.proto";
package test1;

message SearchResponse {
  //包名.訊息名
  repeated test2.Result results = 1;
}
複製程式碼
  • 檔名稱result.proto,在與search_response.proto同級目錄的test下
syntax = "proto3";
package test2;

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
複製程式碼

如果兩個.proto檔案在同一個目錄下直接這樣import "result.proto";倒入即可。

6 內嵌型別

我們還可以在訊息型別中定義訊息,例子如下:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}
複製程式碼

在上面的例子中在SearchResponse訊息體中定義了一個Result訊息並使用。


如果想在其他的訊息體引用Result這個訊息,可以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;
    }
  }
}
複製程式碼

7 Map

proto支援map屬性型別的定義,語法如下:
map<key_type,value_type> map_field = N;
key_type可以是任何整數或字串型別(除浮點型別和位元組之外的任何標量型別,列舉型別也是不合法的key型別),value_type可以是任何型別的資料。


map更具體的使用方式參看API

8 包

可以為proto檔案指定包名,防止訊息命名衝突。


例子如下:

package foo.bar;
message Open { ... }
複製程式碼

當你在為訊息型別定義屬性的時候,你可以通過命名.型別的形式來使用已經定義好的訊息型別,如下:

Message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}
複製程式碼

9 服務定義

如果你想在RPC中使用已經定義好的訊息型別,你可以在.proto檔案中定一個訊息服務介面,protocol buffer編譯器會生成對應語言的介面程式碼。

  • 介面定義例子:
service SearchService {
    //  方法名  方法引數                 返回值
    rpc Search(SearchRequest) returns (SearchResponse); 
}
複製程式碼

10 選項

下面只列出java的.proto檔案常用的一下選賢,其他選項前參看官網文件

  • java_package(檔案選項):指定生成的java類所在的包, 如果在.proto檔案中沒有提供明確的java_package選項,那麼預設情況下,將使用proto包。如果沒有生成java程式碼該選項預設是不生效的。
    option java_package = "org.example.foo";

  • java_multiple_files(檔案選項):指定在proto檔案中定義的所有訊息、列舉和服務在生成java類的時候都會生成對應的java類檔案,而不是以內部類的形式出現。
    option java_multiple_files = true;

  • java_outer_classname(檔案選項):指定生成的java類檔名稱,如果不指定則會預設使用.proto檔案的檔名稱,如果沒有生成java類檔案,則該選項不會生效 <span id="1">Hello World</span>。
    option java_outer_classname = "HelloWorld";


相關文章