Google Protobuf 編解碼

Google Protobuf 優勢:api

  • 在谷歌內部長期使用, 產品成熟度高.
  • 跨語言、支持多種語言, 包括 C++、Java 和 Python.
  • 編碼後的消息更小, 更加有利於存儲和傳輸.
  • 編解碼的性能很是高.
  • 支持不一樣協議版本的前向兼容.
  • 支持定義可選和必選字段.

Protobuf 的入門

Protobuf 是一個靈活、高效、結構化的數據序列化框架, 相比與 xml 等傳統的序列化工具, 它更小、更快、更簡單.數組

Protobuf 支持數據結構化一次能夠處處使用, 甚至跨語言使用, 經過代碼生成工具能夠自動生成不一樣語言版本的源代碼, 甚至能夠在使用不一樣版本的數據結構進程間進行數據傳遞, 實現數據結構前向兼容.數據結構

定義消息類型

syntax = "proto3";

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

該文件的第一行指定使用 proto3 語法, 若是不寫的話表示 proto2.框架

分配字段編號

string query = 1; 1 就是字段編號, 字段號主要用來標識二進制格式字段的. 1 到 15 字段號佔一個字節. 16 到 2047 字段號須要兩個字節.工具

咱們將對象轉換爲報文的時候, 是按照字段編號進行報文封裝的; 咱們接收到數據以後框架會幫咱們按照字段號進行賦值.性能

不能使用數字19000到19999, 由於它們是爲 Google Protobuf 保留的.ui

字段類型對應

.proto Type Notes C++ Type Java Type
double double double
float float float
int32 使用可變長度編碼, 對負數編碼效率低下
若是您的字段可能有負值, 則使用sint32代替.
int32 int
int64 使用可變長度編碼, 對負數編碼效率低下
若是您的字段可能有負值, 則使用sint64代替.
int64 long
uint32 使用可變長度編碼 uint32 int
uint64 使用可變長度編碼 uint64 long
sint32 使用可變長度編碼
有符號的int值這些編碼比常規int32更有效地編碼負數
uint32 int
sint64 使用可變長度編碼
有符號的int值這些編碼比常規int64更有效地編碼負數
int64 long
fixed32 四個字節, 若是值一般大於2的28次方, 則比uint32更有效 uint32 int
fixed64 四個字節, 若是值一般大於2的56次方, 則比uint64更有效 uint64 long
sfixed32 四個字節 int32 int
sfixed64 四個字節 int64 long
bool bool boolean
string 字符串必須始終包含UTF-8編碼或7位ASCII文本 string String
bytes 字符串必須始終包含UTF-8編碼或7位ASCII文本 string ByteString

默認值

  • 對於字符串, 默認值是空字符串.
  • 對於字節, 默認值爲空字節.
  • 對於bool, 默認值爲false.
  • 對於數字類型, 默認值爲零.
  • 對於枚舉, 默認值是第一個定義的枚舉值, 必須爲0.
還請注意, 若是消息字段設置爲默認值, 則該值將不會序列化.

容許嵌套

Protocol Buffers 定義 message 容許嵌套組合成更加複雜的消息google

message SearchResponse {
  repeated Result results = 1;
}

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

更多的例子:編碼

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

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

導入定義

能夠在文件的頂部添加一個import語句:url

import "myproject/other_protos.proto";

未知字段

未知字段就是解析器沒法識別的字段. 例如, 當服務端使用新消息發送數據, 客戶端使用舊消息解析數據, 那麼這些新字段將成爲舊消息中的未知字段.

在3.5和更高版本中, 未知字段在解析過程當中被保留, 幷包含在序列化中輸出.

Map 類型

repeated 類型能夠用來表示數組, Map 類型則能夠用來表示字典.

map<key_type, value_type> map_field = N;

map<string, Project> projects = 3;

key_type 能夠是任何 int 或者 string 類型(任何的標量類型, 具體能夠見上面標量類型對應表格, 可是要除去 floatdoublebytes)

枚舉值也不能做爲 key.

key_type 能夠是除去 map 之外的任何類型.

須要特別注意的是:

  • map 是不能用 repeated 修飾的.
  • map 迭代順序的是不肯定的, 因此你不能肯定 map 是一個有序的.
  • .proto 生成文本格式時, map 按 key 排序. 數字的 key 按數字排序.
  • 從數組中解析或合併時, 若是有重複的 key, 則使用所看到的最後一個 key(覆蓋原則).從文本格式解析映射時, 若是有重複的 key, 解析可能會失敗.

Protocol Buffer 雖然不支持 map 類型的數組, 可是能夠轉換一下, 用如下思路實現 maps 數組:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

上述寫法和 map 數組是徹底等價的,因此用 repeated 巧妙的實現了 maps 數組的需求.

Protocol Buffer 命名規範

message 採用駝峯命名法. message 首字母大寫開頭. 字段名採用下劃線分隔法命名.

message SongServerRequest {
  required string song_name = 1;
}

枚舉類型採用駝峯命名法. 枚舉類型首字母大寫開頭. 每一個枚舉值所有大寫, 而且採用下劃線分隔法命名.

enum Foo {
  FIRST_VALUE = 0;
  SECOND_VALUE = 1;
}

每一個枚舉值用分號結束, 不是逗號.

服務名和方法名都採用駝峯命名法. 而且首字母都大寫開頭.

service FooService {
  rpc GetSomething(FooRequest) returns (FooResponse);
}

經常使用方法

getDefaultInstance(): 返回單例實例, 它與 newBuilder().build() 實例相同
getDescriptor(): 返回類型的描述符. 包括具備哪些字段以及類型. 這能夠與 Message 的反射方法一塊兒使用, 例如getField().
parseFrom(...): 返回反序列化後的 Message. 注意不會拋出 UninitializedMessageExceptionInvalidProtocolBufferException 異常.
Message.Builder: 中的 mergeFrom() 放會將數據解析爲此類型的消息, 並進行消息合併.
newBuilder(): 建立一個新的構建器.

Any

Any類型容許包裝任意的message類型:

import "google/protobuf/any.proto";

message Response {
    google.protobuf.Any data = 1;
}

總結

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

能夠經過 pack()unpack()(方法名在不一樣的語言中可能不一樣)方法裝箱/拆箱,如下是Java的例子:

People people = People.newBuilder().setName("proto").setAge(1).build();
// protoc編譯後生成的message類
Response r = Response.newBuilder().setData(Any.pack(people)).build();
// 使用Response包裝people

System.out.println(r.getData().getTypeUrl());
// type.googleapis.com/example.protobuf.people.People
System.out.println(r.getData().unpack(People.class).getName());
// proto

Oneof

若是你有一些字段同時最多隻有一個能被設置, 可使用 oneof 關鍵字來實現, 任何一個字段被設置, 其它字段會自動被清空(被設爲默認值):

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

默認值

好比咱們建立了上面的消息類型, 咱們在代碼中設置 builder.setSubReqID(0); 爲 0, 零是數值類型的默認值; 因此咱們會看到序列化後的數據中, 沒有對此字段進行序列化.

byte[] arry = builder.build().toByteArray();

arry 長度爲 0. 對於字段類型是 string 類型的也是同樣的; 也就是說顯示賦值默認值也不會對其進行序列化.

保留字段

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

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

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

保留後咱們沒法對其設置或序列化和反序列化.

相關文章
相關標籤/搜索