Protobuf數據格式解析

Protobuf數據格式解析

Protobuf是Google開源的一款類似於Json,XML數據交換格式,其內部數據是純二進制格式,不依賴於語言和平臺,具有簡單,數據量小,快速等優點。目前用於序列化與反序列化官方支持的語言有C++,C#, GO, JAVA, PYTHON。適用於大小在1M以內的數據,因爲像在移動設備平臺,內存是很珍貴。

使用方法也比較簡單:

  • 定義用於消息文件.proto
  • 使用protobuf的編譯器編譯消息文件
  • 使用編譯好對應語言的類文件進行消息的序列化與反序列化

先來定義一個簡單的消息:

message Person {
   int32 id = 1;//24
   string name = 2;//wujingchao
   string email = 3;//[email protected]
}

實際的二進制消息爲:

08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16 77 75 6a 69 6e 67 63 68 61 6f 39 32 40 67 6d 61 69 6c 2e 63 6f 6d

下面就講解這段二進制流數據是怎麼組成的:

Varints

一般情況下int類型都是固定4個字節,protobuf定義了一種變長的int,每個字節最高位表示後面還有沒有字節,低7位就爲實際的值,並且使用小端的表示方法。例如1,varint的表示方法就爲:

0000 0001

是不是這樣就省了三個字節。

再例如300,4字節表示爲:10 0101100,varint表示爲:

10101100 00000010

所以前面消息爲Person的id的值爲00011000,即0x18。

負數的最高位爲1,如果負數也使用這種方式表示就會出現一個問題,int32總是需要5個字節,int64總是需要10個字節。

所以定義了另外一種類型:sint32,sint64。採用ZigZag編碼,所有的負數都使用正數表示,計算方式:

  • sint32
    (n << 1) ^ (n >> 31)
  • sint64
    (n << 1) ^ (n >> 63)
Signed Original Encoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

使用Varint編碼的類型有int32, int64, uint32, uint64, sint32, sint64, bool, enum。Java裏面沒有對應的無符號類型,int32與uint32一樣。

Wire Type

每個消息項前面都會有對應的tag,才能解析對應的數據類型,表示tag的數據類型也是Varint。

tag的計算方式: (field_number << 3) | wire_type

每種數據類型都有對應的wire_type:

Wire Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

所以wire_type最多隻能支持8種,目前有6種。

所以前面Person的id,field_number爲1,wire_type爲0,所以對應的tag爲

1 <<< 3 | 0  = 0x08

Person的name,field_number爲2,wire_type爲2,所以對應的tag爲

2 <<< 3 | 2 = 0x12

對應Length-delimited的wire type,後面緊跟着的Varint類型表示數據的字節數。

所以name的tag後面緊跟的0x0a表示後面的數據長度爲10個字節,即"wujingchao"的UTF-8 編碼或者ASCII值:

08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16

嵌套的消息類型embedded messages與packed repeated fields也是使用這種方式表示,對應默認值的數據,是不會寫進protobuf消息裏面的。

packed repeated與repeated的區別在於編碼方式不一樣,repeated將多個屬性類型與值分開存儲。而packed repeated採用Length-delimited方式。下面這個是官方文檔的例子:

message Test4 {
    repeated int32 d = 4 [packed=true];
}

22        // tag (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

如果沒有packed的屬性是這樣存儲的:

20 //tag(field number 4,wire type 0)
03 //first element (varint 3)
20 //tag(field number 4,wire type 0)
8E 02//second element (varint 270)
20 //tag(field number 4,wire type 0)
9E A7 05  // third element (varint 86942)

是不是這種方式比較節省內存,所以proto3的repeated默認就是使用packed這種方式來存儲。(proto2與proto3區別在於.proto的語法)。

有了以上的相關概念,我們在讀protobuf的源碼就比較容易了。

參考:https://developers.google.com/protocol-buffers/docs/encoding

protof的描述

首先 protobuf是一個開源項目,是goole內部久經考驗的一個東西。主要用於結構化數據串行化的靈活、高效、自動的方法,有如XML,不過他更小,更快,也更簡單。你可以定義自己的數據結構,然後使用代碼生成器生成的代碼來讀寫這個數據結構。甚至可以在無需重新部署程序的情況下更新數據結構。

protobuf的優點:

1、性能好/效率高

時間開銷: XML格式化(序列化)的開銷還好;但是XML解析(反序列化)的開銷就不敢恭維了。 但是protobuf在這個方面就進行了優化。可以使序列化和反序列化的時間開銷都減短。

空間開銷:也減少了很多

2、有代碼生成機制

比如你你寫個一下類似結構體的內容

[cpp] view plain copy
  1. message testA  
  2. {  
  3.     required int32 m_testA = 1;  
  4. }  
向寫一個這樣的機構可以自動生成它的.h 文件和點.cpp文件。  他將對結構體testA的操作封裝成一個類。

3、支持向後兼容和向前兼容

當客戶端和服務器同事使用一塊協議的時候, 當客戶端在協議中增加一個字節,並不會影響客戶端的使用

4、支持多種編程語言

在Google官方發佈的源代碼中包含了c++、JavaPython三種語言

protobuf的缺陷

1、二進制格式導致可讀性差

爲了提高性能,protobuf採用了二進制格式進行編碼。這直接導致了可讀性差。

2、缺乏自描述

一般來說,XML是自描述的,而protobuf格式則不是。 給你一段二進制格式的協議內容,不配合你寫的結構體是看不出來什麼作用的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章