Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
簡單來說组哩,Protocol Buffers 是一種和語言平臺都沒關(guān)的數(shù)據(jù)交換格式。
關(guān)于 Protobuf 在iOS下的使用請看上篇文章 iOS 的 Protocol Buffer 簡單使用
Varint
Protobuf 序列化后的二進(jìn)制數(shù)據(jù)消息非常的緊湊,這得益于 Protobuf 所采用的 Varint
Varint 是一種緊湊的表示數(shù)字的方法,它用一個或多個字節(jié)來表示一個數(shù)字,值越小的數(shù)字使用越少的字節(jié)數(shù)揣钦。這能減少用來表示數(shù)組的字節(jié)數(shù)。
比如對于 int32 類型的數(shù)字,一般需要4個 byte 來標(biāo)識聂抢。但是采用 Varint,對于很小的 int32 類型的數(shù)字棠众,也能用1個 byte 來標(biāo)識琳疏。如果數(shù)字很大,也就需要5個 byte 來表示了闸拿。但是空盼,一般情況下很少會出現(xiàn)數(shù)字都是大數(shù)的情況下轻腺。
正常情況下虑椎,每個 byte 的8個 bit 位都用于存儲數(shù)據(jù)用提澎,而在 Varint 中肛度,每個 byte 的最高位的 bit 有著特殊的含義咖杂,如果該位為1鸽扁,表示后續(xù)的 byte 也是該數(shù)據(jù)的一部分谭网;如果該位為0致份,則結(jié)束智袭。其他的7個 bit 位都用來表示數(shù)據(jù)奔缠。因此小于127的 int32 數(shù)字都可以用一個 byte 表示,而大于等于 128 的數(shù)字:如128吼野,則會用兩個字節(jié)表示:1000 0000 0000 0001
(采用的是小端模式)校哎,311則表示:1011 0111 0000 0010
下圖演示了 Protobuf 如果通過2個 byte 解析出 128。Protobuf 字節(jié)序采用的是 little-endian(小端模式)
int32 數(shù)據(jù)類型能表示負(fù)數(shù)瞳步,負(fù)數(shù)的最高位為1闷哆,如果負(fù)數(shù)也使用這種方式表示會出現(xiàn)一個問題,int32 總是需要5個字節(jié)单起,int64 總是需要10個字節(jié)抱怔。所以 Protobuf 定義了另外一種類型 sint32, sint64,采用 ZigZag 編碼嘀倒,所有的負(fù)數(shù)都使用正數(shù)表示屈留,計(jì)算方式為:
-
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 |
Message Structure
Protobuf 消息是一系列的鍵值對組成局冰。消息的二進(jìn)制版本僅使用 field 數(shù)字當(dāng)作 key,不同 field 的屬性和類型只能通過消息類型的定義 (即 .proto 文件) 在解碼端確定灌危。如果消息中不存在該 field康二,那么序列化后的 Message Buffer 中也不會有該 field,這些特性都有助于節(jié)約消息本身的大小勇蝙。
Key 用來標(biāo)識具體的 field沫勿,在解包的時候,Protobuf 根據(jù) key 就能知道相應(yīng)的 Value 對應(yīng)于消息中的哪一個field味混,數(shù)據(jù)類型是哪個類型产雹。
Key 的定義如下:
(field_number << 3) | wire_type
Key 由兩部分組成:第一個部分是 field_number,比如上篇文章定義的消息 FooSimpleMessage 中的 msgId 屬性的 field_number 為1翁锡;第二部分為 wire_type蔓挖,表示 Value 的傳輸類型
表1. Wire Type
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 |
在之前的例子中,msgId 采用的數(shù)據(jù)類型為 int32盗誊,因此對應(yīng)的 wire_type 為0时甚,所以對應(yīng)的 tag 為
(1 << 3) | 0 = 0x08
FooSimpleMessage 的 msgContent,field_number 為2哈踱,wire_type 為2,所以對應(yīng)的 tag 為
(2 << 3) | 2 = 0x12
對應(yīng) Length-delimited 的 wire type梨熙,后面緊跟著的 Varint 類型表示數(shù)據(jù)的字節(jié)數(shù)开镣。所以 msgContent 的 key 后面緊跟著的 0x1a
表示后面的數(shù)據(jù)長度為10個字節(jié),"A protobuf message content" 的 ASCII 值即為:0x41 0x20 0x70 0x72 0x6f 0x74 0x6f 0x62 0x75 0x66 0x20 0x6d 0x65 0x73 0x73 0x61 0x67 0x65 0x20 0x63 0x6f 0x6e 0x74 0x65 0x6e 0x74
在 Demo 里面定義的 msg 對象咽扇,其序列化后的數(shù)據(jù)的十六進(jìn)制表示應(yīng)該為 0801121a 41207072 6f746f62 7566206d 65737361 67652063 6f6e7465 6e74
FooSimpleMessage *msg = [[FooSimpleMessage alloc] init];
msg.msgId = 1;
msg.msgContent = @"A protobuf message content";
NSLog("%@", msg.data);
運(yùn)行demo邪财,打印一下結(jié)果和猜想的一樣:
<0801121a 41207072 6f746f62 7566206d 65737361 67652063 6f6e7465 6e74>