ProtoBuf格式详解
“介紹protobuf編碼格式。”
protobuf是一種數(shù)據(jù)交換格式,又稱PB編碼,由Google開源,類似于Json、XML,但其內(nèi)部是純二進(jìn)制格式,比Json,XML等格式要更精煉,主要用于數(shù)據(jù)的序列化和反序列化,目前官方提供了JAVA、Python、C++等多種語言的實現(xiàn)。
PB格式的解析依賴于消息文件,在其實現(xiàn)中,.proto定義了各個消息項的id值。
直觀地,PB編碼就是將一個結(jié)構(gòu)體的內(nèi)容編碼成二進(jìn)制流。例如一段json數(shù)據(jù):
{?
?"id":176,
?"age":24,
?"name":"xieyifenxi",
}
.proto文件的定義如下:
message Person {?
required int32 id = 1;
optional int32 age = 2;
required string name = 3;
}
則json數(shù)據(jù)編碼成PB格式則是:
08 b0 01 10 18 1a 0a?78 69 65 79 69 66 65 6E 78 69
通常,在協(xié)議解析的過程中碰到的PB編碼,是沒有.proto文件的,解析的時候,只需要根據(jù)數(shù)據(jù)內(nèi)容,解析出每一項內(nèi)容即可,而每一項內(nèi)容的含義,一般通過分析得到。
在許多APP的數(shù)據(jù)流中,都會存在protobuf編碼。本文將通過對PB編碼進(jìn)行介紹,使大家了解如何在協(xié)議分析過程中對其進(jìn)行解析。
01
—
數(shù)據(jù)結(jié)構(gòu)
通過前面的例子,可以看到PB的數(shù)據(jù)結(jié)構(gòu)就是每項數(shù)據(jù)獨立編碼,包含一個表示數(shù)據(jù)類型wire_type和字段序號field_number的數(shù)據(jù)頭,和對應(yīng)的數(shù)據(jù)段內(nèi)容。即HEAD1+MSG1+HEAD2+MSG2+……
在數(shù)據(jù)頭中,字段的序號field_number在整個數(shù)據(jù)結(jié)構(gòu)中是唯一的,并且可以亂序、缺失和嵌套,序號是在.proto文件中定義的,協(xié)議分析中關(guān)心的意義不是很大。
數(shù)據(jù)類型wire_type則表示數(shù)據(jù)段內(nèi)容是什么類型,在protobuf官網(wǎng)上描述了類型的含義:
https://developers.google.com/protocol-buffers/docs/encoding
常見的數(shù)據(jù)類型wire_type為0和2,分別為Varint類型和Length-delimited變長度數(shù)據(jù)類型,掌握了這兩個類型,基本上在協(xié)議解析中,處理PB編碼就基本沒有障礙了。Varint類型一般就是int數(shù)據(jù),而Length-delimited變長度數(shù)據(jù)類型通常就是字符串?dāng)?shù)組等數(shù)據(jù)。
數(shù)據(jù)頭中數(shù)據(jù)類型和字段序號的組合方式是:(field_number << 3) | wire_type
當(dāng)然,field_number是Varint類型,需遵循Varint的編碼規(guī)則。
例如,文首的例子中,id、age、name對應(yīng)的編碼值為:
1 <<< 3 | 0 =0x08
2 <<< 3 | 0 = 0x10
3 <<< 3 | 2 = 0x1a
數(shù)據(jù)頭之后,是數(shù)據(jù)段,它包含了被編碼的數(shù)據(jù),不同類型的數(shù)據(jù)編碼格式不同,后面的章節(jié)將介紹對應(yīng)具體類型的編碼方法。
02
—
Varint
Varint是一種對數(shù)字進(jìn)行編碼的方法,將數(shù)字編碼成不定長的二進(jìn)制數(shù)據(jù),數(shù)值越小,編碼后的字節(jié)越少。
編碼規(guī)則如下:
每個字節(jié)的最高位表示下一字節(jié)是否仍然是編碼的內(nèi)容,若最高位為1,則下一字節(jié)仍然是編碼的數(shù)字的一部分,若該位為0,則編碼到本字節(jié)結(jié)束。每個字節(jié)的后7位,則由小端表示的數(shù)字的二進(jìn)制值,在高位補0湊齊7的倍數(shù)位組成。
例如,數(shù)值345,其二進(jìn)制值為 1 0101 1001,在高位補0后分成兩個7位 000 0010和?101 1001,則Varint編碼結(jié)果為:
1 101 1001 0?000 0010
即0xD9 0x02
對文首的例子,由于id?176的二進(jìn)制值為1011 0000,每七位編碼成一個字節(jié),因此,需要用兩個字節(jié)來表示:
1011 0000 0000 0001
即0xB0 0x01
而age?24的二進(jìn)制值為?1 1000,則只需要一個字節(jié)來表示:
0001 1000
即0x18
前面只是弄明白了int32的Varint編碼,對協(xié)議解析來說,一般已經(jīng)夠用了,除非這個被編碼的數(shù),在取出后需要用其特定的含義來進(jìn)行計算,因為在PB編碼中,還考慮了對負(fù)數(shù)進(jìn)行Varint編碼。
當(dāng)我們按照同樣的邏輯對負(fù)數(shù)進(jìn)行Varint編碼時,會發(fā)現(xiàn),負(fù)數(shù)編碼后占用的字節(jié)會很多,這不太合算,因此ZigZag編碼在PB中被使用,使得Varint編碼可以用較少的位數(shù)來對負(fù)數(shù)進(jìn)行編碼。
PB編碼中提供了sint32和sint64類型,使用ZigZag編碼,讓所有的負(fù)數(shù)都使用正數(shù)表示,計算方式如下:
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。
依此類推
在協(xié)議還原中,對一個Varint編碼的值,想要知道它表達(dá)的是int32,還是sint32,就只有想辦法找到其對應(yīng)的.proto文件才可以。
03
—
Length-delimited
Length-delimited就是對可變長度的數(shù)據(jù),在編碼時,將長度和數(shù)據(jù)編碼在一起,類似于TLV結(jié)構(gòu)的LV部分,前面為數(shù)據(jù)長度,后面為由數(shù)據(jù)長度決定的數(shù)據(jù)內(nèi)容,數(shù)據(jù)長度采用的是Varint編碼。
例如文首的例子里,name的值為"xieyifenxi"的長度為10,則編碼為:
0a?78 69 65 79 69 66 65 6E 78 69
其中,0x0a為長度值的Varint編碼,之后緊接著的是值的內(nèi)容。
這相當(dāng)?shù)暮唵巍?/p>
對protobuf編碼的詳解就介紹到這里了,有疑問,可以聯(lián)系我,或者上其官網(wǎng)了解。它的官網(wǎng)是https://developers.google.com/protocol-buffers/
長按進(jìn)行關(guān)注。
總結(jié)
以上是生活随笔為你收集整理的ProtoBuf格式详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 车联网APP,安全设施薄弱的山寨品
- 下一篇: HTTP协议解析之Cookie