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 是一种和语言平台都没关的数据交换格式。
关于 Protobuf 在iOS下的使用请看上篇文章 iOS 的 Protocol Buffer 简单使用
Varint
Protobuf 序列化后的二进制数据消息非常的紧凑,这得益于 Protobuf 所采用的 Varint
Varint 是一种紧凑的表示数字的方法,它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数组的字节数。
比如对于 int32 类型的数字,一般需要4个 byte 来标识。但是采用 Varint,对于很小的 int32 类型的数字,也能用1个 byte 来标识。如果数字很大,也就需要5个 byte 来表示了。但是,一般情况下很少会出现数字都是大数的情况下。
正常情况下,每个 byte 的8个 bit 位都用于存储数据用,而在 Varint 中,每个 byte 的最高位的 bit 有着特殊的含义,如果该位为1,表示后续的 byte 也是该数据的一部分;如果该位为0,则结束。其他的7个 bit 位都用来表示数据。因此小于127的 int32 数字都可以用一个 byte 表示,而大于等于 128 的数字:如128,则会用两个字节表示:1000 0000 0000 0001
(采用的是小端模式),311则表示:1011 0111 0000 0010
下图演示了 Protobuf 如果通过2个 byte 解析出 128。Protobuf 字节序采用的是 little-endian(小端模式)
int32 数据类型能表示负数,负数的最高位为1,如果负数也使用这种方式表示会出现一个问题,int32 总是需要5个字节,int64 总是需要10个字节。所以 Protobuf 定义了另外一种类型 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 |
Message Structure
Protobuf 消息是一系列的键值对组成。消息的二进制版本仅使用 field 数字当作 key,不同 field 的属性和类型只能通过消息类型的定义 (即 .proto 文件) 在解码端确定。如果消息中不存在该 field,那么序列化后的 Message Buffer 中也不会有该 field,这些特性都有助于节约消息本身的大小。
Key 用来标识具体的 field,在解包的时候,Protobuf 根据 key 就能知道相应的 Value 对应于消息中的哪一个field,数据类型是哪个类型。
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 采用的数据类型为 int32,因此对应的 wire_type 为0,所以对应的 tag 为
(1 << 3) | 0 = 0x08
FooSimpleMessage 的 msgContent,field_number 为2,wire_type 为2,所以对应的 tag 为
(2 << 3) | 2 = 0x12
对应 Length-delimited 的 wire type,后面紧跟着的 Varint 类型表示数据的字节数。所以 msgContent 的 key 后面紧跟着的 0x1a
表示后面的数据长度为10个字节,"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 对象,其序列化后的数据的十六进制表示应该为 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);
运行demo,打印一下结果和猜想的一样:
<0801121a 41207072 6f746f62 7566206d 65737361 67652063 6f6e7465 6e74>