ProtoBuf格式詳解


介紹protobuf編碼格式。


protobuf是一種數據交換格式,又稱PB編碼,由Google開源,類似於Json、XML,但其內部是純二進制格式,比Json,XML等格式要更精煉,主要用於數據的序列化和反序列化,目前官方提供了JAVA、Python、C++等多種語言的實現。


PB格式的解析依賴於消息文件,在其實現中,.proto定義了各個消息項的id值。


直觀地,PB編碼就是將一個結構體的內容編碼成二進制流。例如一段json數據:

 "id":176,

 "age":24,

 "name":"xieyifenxi",

}


.proto文件的定義如下:

message Person { 

required int32 id = 1;

optional int32 age = 2;

required string name = 3;

}

則json數據編碼成PB格式則是:

08 b0 01 10 18 1a 0a 78 69 65 79 69 66 65 6E 78 69


通常,在協議解析的過程中碰到的PB編碼,是沒有.proto文件的,解析的時候,只需要根據數據內容,解析出每一項內容即可,而每一項內容的含義,一般通過分析得到。


在許多APP的數據流中,都會存在protobuf編碼。本文將通過對PB編碼進行介紹,使大家了解如何在協議分析過程中對其進行解析。


01

數據結構


通過前面的例子,可以看到PB的數據結構就是每項數據獨立編碼,包含一個表示數據類型wire_type和字段序號field_number的數據頭,和對應的數據段內容。即HEAD1+MSG1+HEAD2+MSG2+……


在數據頭中,字段的序號field_number在整個數據結構中是唯一的,並且可以亂序、缺失和嵌套,序號是在.proto文件中定義的,協議分析中關心的意義不是很大。


數據類型wire_type則表示數據段內容是什么類型,在protobuf官網上描述了類型的含義:

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


常見的數據類型wire_type02,分別為Varint類型和Length-delimited變長度數據類型,掌握了這兩個類型,基本上在協議解析中,處理PB編碼就基本沒有障礙了。Varint類型一般就是int數據,而Length-delimited變長度數據類型通常就是字符串數組等數據。


數據頭中數據類型和字段序號的組合方式是:(field_number << 3) | wire_type

當然,field_number是Varint類型,需遵循Varint的編碼規則。


例如,文首的例子中,id、age、name對應的編碼值為:

1 <<< 3 | 0 =0x08

2 <<< 3 | 0 = 0x10

3 <<< 3 | 2 = 0x1a


數據頭之后,是數據段,它包含了被編碼的數據,不同類型的數據編碼格式不同,后面的章節將介紹對應具體類型的編碼方法。


02


Varint


Varint是一種對數字進行編碼的方法,將數字編碼成不定長的二進制數據,數值越小,編碼后的字節越少。


編碼規則如下:

每個字節的最高位表示下一字節是否仍然是編碼的內容,若最高位為1,則下一字節仍然是編碼的數字的一部分,若該位為0,則編碼到本字節結束。每個字節的后7位,則由小端表示的數字的二進制值,在高位補0湊齊7的倍數位組成。


例如,數值345,其二進制值為 1 0101 1001,在高位補0后分成兩個7位 000 0010和 101 1001,則Varint編碼結果為:

1 101 1001 0 000 0010

即0xD9 0x02


對文首的例子,由於id 176的二進制值為1011 0000,每七位編碼成一個字節,因此,需要用兩個字節來表示:

1011 0000 0000 0001

0xB0 0x01


而age 24的二進制值為 1 1000,則只需要一個字節來表示:

0001 1000

即0x18


前面只是弄明白了int32的Varint編碼,對協議解析來說,一般已經夠用了,除非這個被編碼的數,在取出后需要用其特定的含義來進行計算,因為在PB編碼中,還考慮了對負數進行Varint編碼


當我們按照同樣的邏輯對負數進行Varint編碼時,會發現,負數編碼后占用的字節會很多,這不太合算,因此ZigZag編碼在PB中被使用,使得Varint編碼可以用較少的位數來對負數進行編碼。


PB編碼中提供了sint32和sint64類型,使用ZigZag編碼,讓所有的負數都使用正數表示,計算方式如下:

sint32:

(n << 1) ^ (n >> 31)

sint64:

(n << 1) ^ (n >> 63)


即:

原始值0,通過計算,得到ZigZag表示值為0;

原始值-1,通過計算,得到ZigZag表示值為1;

原始值1,通過計算,得到ZigZag表示值為2;

原始值-2,通過計算,得到ZigZag表示值為3;

原始值2147483647,通過計算,得到ZigZag表示值為4294967294;

原始值-2147483648,通過計算,得到ZigZag表示值為4294967295。

依此類推


在協議還原中,對一個Varint編碼的值,想要知道它表達的是int32,還是sint32,就只有想辦法找到其對應的.proto文件才可以。


03


Length-delimited


Length-delimited就是對可變長度的數據,在編碼時,將長度和數據編碼在一起,類似於TLV結構的LV部分,前面為數據長度,后面為由數據長度決定的數據內容,數據長度采用的是Varint編碼。


例如文首的例子里,name的值為"xieyifenxi"的長度為10,則編碼為:

0a 78 69 65 79 69 66 65 6E 78 69


其中,0x0a為長度值的Varint編碼,之后緊接着的是值的內容。


這相當的簡單。




對protobuf編碼的詳解就介紹到這里了,有疑問,可以聯系我,或者上其官網了解。它的官網是https://developers.google.com/protocol-buffers/


640?wx_fmt=jpeg

長按進行關注。






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM