Protobuf 語言指南(proto3)

定義消息類型

首先讓咱們看一個很是簡單的例子. 假設要定義搜索請求消息格式, 其中每一個搜索請求都有一個查詢字符串, 您感興趣的特定結果頁面以及每頁的一些結果. 這是用於定義消息類型的 .proto 文件.java

syntax = "proto3";

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

注意如下幾點:數組

  • 該文件的第一行指定使用 proto3 語法; 若是不寫則使用 proto2.
  • 一個消息對應一個字段, 一個字段對應一個數據類型.
值得注意的是: 字段類型能夠是枚舉或其餘數據類型.

字段編號

好比 string query = 1; 字段, 1 就是字段編號(unique number).ide

字段編號的範圍爲 1 到 536,870,911. 不能使用數字 19000 到 19999, 由於它們是爲 Google Protobuf 保留的.ui

在同一個 .proto 文件中添加多個消息類型

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

message SearchResponse {
 ...
}

保留字段

當定義好字段後, 在後續開發中發現某個字段根本沒用.this

例如 string userName = 2; 字段, 這個時候最好不要進行註釋或刪除.google

有可能之後加載相同的舊版本, 這可能會致使數據損壞, 隱私錯誤等. 確保不會發生這種狀況的一種方法是指定要刪除的字段爲保留字段.url

message SubscribeReq {
  
  reserved 2;
  
  int32 subReqID = 1;
  string userName = 2;
  string productName = 3;
  string address = 4;
}

顧名思義, 就是此字段會被保留可能在之後會使用此字段. 使用關鍵字 reserved 表示要保留字段編號爲 2.code

上面代碼咱們在生成 Java 文件的時候會出現 ubscribeReqPeoro.proto: Field "userName" uses reserved number 2 錯誤信息, 因此須要將 string userName = 2; 註釋, 或者刪除.對象

值得注意的是: 您不能在同一 reserved 語句中混合字段名稱和字段編號. 下面是表準格式:繼承

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

指定字段規則

所指定的消息字段修飾符必須是以下之一:

  • singular: 一個格式良好的消息應該有0個或者1個這種字段 (可是不能超過1個).
  • repeated: 一個格式良好的消息中, 這種字段能夠重複任意屢次 (包括 0 次). 重複值的順序會被保留.

枚舉

當須要定義一個消息類型的時候, 可能想爲一個字段指定某 「預約義值序列」 中的一個值.

例如, 假設要爲每個 SearchRequest 消息添加一個 corpus 字段, 而 corpus 的值多是 UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS 或 VIDEO 中的一個.

其實能夠很容易地實現這一點: 經過向消息定義中添加一個枚舉 (enum) 而且爲每一個可能的值定義一個常量就能夠了.

在下面的例子中, 在消息格式中添加了一個叫作 Corpus 的枚舉類型——它含有全部可能的值——以及一個類型爲 Corpus 的字段:

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;
}

值得注意的是:
每一個枚舉類型必須將其第一個類型映射爲 0, 這是由於:

  • 必須有有一個 0 值, 咱們能夠用這個 0 值做爲默認值.
  • 這個零值必須爲第一個元素, 爲了兼容 proto2 語義, 枚舉類的第一個值老是默認值.

另外, 枚舉常量必須在 32 位整數範圍內, 而且儘可能不要使用負數.

你能夠經過將不一樣的枚舉常量指定爲相同的值. 若是這樣作你須要將 allow_alias 設置爲 true.

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

其它消息類型

你能夠將其餘消息類型用做字段類型. 例如, 假設在每個 SearchResponse 消息中包含 Result 消息, 此時能夠在相同的 .proto 文件中定義一個 Result 消息類型, 而後在 SearchResponse 消息中指定一個 Result 類型的字段, 如:

message SearchResponse {
  repeated Result results = 1;
}
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

嵌套類型

你能夠在其餘消息類型中定義、使用消息類型, 在下面的例子中, Result 消息就定義在 SearchResponse 消息內, 如:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

若是你想在它的父消息類型的外部重用這個消息類型, 你須要以 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;
    }
  }
}

導入定義

在上面的示例中, ResultSearchResponse 消息類型在同一文件中的, 若是要用做字段類型的消息類型在另外一個 .proto 文件中定義, 該怎麼辦?

能夠 .proto 經過導入來使用其餘文件中的定義. 須要在文件頂部添加 import 語句:

import "myproject/other_protos.proto";

默認狀況下, 使用直接導入的 .proto 文件中的定義.

可是, 有時可能須要將 .proto 文件移動到新位置. 若是直接移動文件位置可能要修改許多代碼位置.

咱們可使用 import public 來解決這個問題.

實例代碼:

// new.proto
// All definitions are moved here
// old.proto
//This is the proto that all clients are importing.
import public「new.proto」;
import「other.proto」;
// client.proto
import "old.proto";
//您使用old.proto和new.proto中的定義,但不使用other.proto

Maps

若是要在數據定義中建立關聯映射, 協議緩衝區提供了一種方便的快捷方式語法:

map < key_type ,value_type > map_field = N ;

key_type 能夠是任何整數或字符串類型.
value_type 能夠是除了 map 的其餘類型.

例如, 若是要建立項目映射, 其中每條 Project 消息都與字符串鍵相關聯, 則能夠像下面這樣定義它:

map < string,Project > projects = 3;
值得注意的是: map 不能使用 repeated.

您能夠向 .proto 文件添加 package 可選說明符, 以防止協議消息類型之間的名稱衝突.

package foo.bar;
message Open { ... }

而後, 您能夠在定義消息類型的字段時使用包說明符:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

Any (任意類型)

// new.proto
syntax = "proto3";

import "google/protobuf/any.proto";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  repeated google.protobuf.Any result_per_page = 3;
}

編譯的時候須要將 google/protobuf/any.protonew.proto 文件放到一塊兒. 要注意目錄結構.

而後執行下面命令進行編譯:

sudo ./protoc -I=/home/sc-ik/桌面/  --java_out=./java /home/sc-ik/桌面/*.proto

在對 result_per_page 進行賦值時, 須要用到 Any 類中的 public static <T extends com.google.protobuf.Message> Any pack(T message) 方法.

在 java 代碼中, 先建立 SearchRequest 對象, 而後對其餘兩個屬性進行賦值.

New.SearchRequest.Builder searchRequest = New.SearchRequest.newBuilder();
searchRequest.setQuery("test");
searchRequest.setPageNumber(10086);

在對 result_per_page 進行賦值時, 須要注意: pack 方法的參數類型爲 com.google.protobuf.Message 接口.

生成的 java 代碼中, 類是繼承自 GeneratedMessageV3.

public static final class SearchRequest extends
            com.google.protobuf.GeneratedMessageV3 implements
            // @@protoc_insertion_point(message_implements:SearchRequest)
            SearchRequestOrBuilder {
        private static final long serialVersionUID = 0L;

可是若是將result_per_page 賦值爲 SearchRequest, 應該怎麼操做呢?

重點就是繼承的 GeneratedMessageV3 抽象類, 這個抽象類又繼承了 AbstractMessage 抽象類, 而 AbstractMessage 抽象類就是 com.google.protobuf.Message 接口的實現.

public abstract class AbstractMessage
    // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType.
    extends AbstractMessageLite implements Message {

因此咱們可使用如下方法進行賦值.

searchRequest.addResultPerPage(
        com.google.protobuf.Any.pack(
                searchRequest.build()
        )
);

pack() 方法我我的理解爲序列化, 那麼和他對應的是反序列化 unpack().

// 將接收到的字節數組飯序列化.
New.SearchRequest.Builder builder2 = New.SearchRequest.newBuilder().mergeFrom(bytes1);

// 獲取到 result_per_page 字段的值.
Any.Builder resultPerPageBuilder = builder2.getResultPerPageBuilder(0);

// 而後使用 unpack 將任意類型 進行反序列化, 獲得想要的數據.
New.SearchRequest unpack1 = resultPerPageBuilder.build().unpack(New.SearchRequest.class);

編譯

編譯命令:

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/*.proto

以下:

sudo ./protoc -I=/home/sc-ik/桌面/ --java_out=./java /home/sc-ik/桌面/*.proto

各類語言的文件參數以及 API
https://developers.google.com...

相關文章
相關標籤/搜索