Google Protobuf 優勢:api
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 |
還請注意, 若是消息字段設置爲默認值, 則該值將不會序列化.
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和更高版本中, 未知字段在解析過程當中被保留, 幷包含在序列化中輸出.
repeated
類型能夠用來表示數組, Map
類型則能夠用來表示字典.
map<key_type, value_type> map_field = N; map<string, Project> projects = 3;
key_type
能夠是任何 int
或者 string
類型(任何的標量類型, 具體能夠見上面標量類型對應表格, 可是要除去 float
、double
和 bytes
)
枚舉值也不能做爲 key.
key_type
能夠是除去 map
之外的任何類型.
須要特別注意的是:
repeated
修飾的..proto
生成文本格式時, map 按 key 排序. 數字的 key 按數字排序.Protocol Buffer 雖然不支持 map 類型的數組, 可是能夠轉換一下, 用如下思路實現 maps 數組:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
上述寫法和 map 數組是徹底等價的,因此用 repeated 巧妙的實現了 maps 數組的需求.
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
. 注意不會拋出 UninitializedMessageException
和 InvalidProtocolBufferException
異常.Message.Builder
: 中的 mergeFrom()
放會將數據解析爲此類型的消息, 並進行消息合併.newBuilder()
: 建立一個新的構建器.
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
關鍵字來實現, 任何一個字段被設置, 其它字段會自動被清空(被設爲默認值):
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;
註釋, 或者刪除.
保留後咱們沒法對其設置或序列化和反序列化.