目录
为了理解Protobuf的编码方式,需要先了解Varints编码。
protobuf的消息是一系列的键值对,消息的二进制格式仅仅是用字段的数字作为key。每个字段的名字和声明的类型仅仅在解码的最后通过引用消息类型定义(例如.proto文件)来检测。
当消息被编码时, key和value编码为字节流。当消息被解码时,解析器需要能跳过无法识别的字段。这样,新的字段可以被增加到消息而不破坏读旧消息。在一个消息中每个键值对的key事实上有两个值:数字编号、数据类型。
流化过程中每个key:varint (field_number << 3) | wire_type,后三位保存着类型。
让我们再来看我们的简单例子. 现在你知道在流的第一个数字中总是一个varint key, 这里是08, 或则(去掉msb):
000 1000
取后三位可以得到类型(0), 然后位移3位可以得到字段数字(1). 所以现在你知道标签是1, 后面的值是一个varint. 使用从上一章得到的varint解码知识, 可以知道后面2个字节存储的值是150。
- 96 01 = 1001 0110 0000 0001
- → 000 0001 ++ 001 0110 (去掉 msb 并掉转7 bits的组)
- → 10010110
- → 2 + 4 + 16 + 128 = 150
如果用int32或者int64作为一个负数的类型,所得结果的varint总是10个字节长度。它被当成一个非常巨大的无符号整型处理。如果使用有符号类型,所得结果的varint使用更有效率的ZigZag编码。
ZigZag编码将有符号整型映射到无符号整型,所有绝对值小的值(比如-1)数字会得到一个小的varint编码值。实现的方式是"zig-zags",在正数和负数整型之间来回摇摆,因此-1被编码为1,1被编码为2, -2被编码为3, 由此类推。在下面的表格中可以看到:
(n << 1) ^ (n >> 31)
(n << 1) ^ (n >> 63)
字符串以test为例子:
内嵌消息:
这里是带有一个内嵌消息的消息定义,内嵌消息是我们的范例类型 Test1:
- message Test1 {
- required int32 a = 1;
- }
-
- message Test2 {
- required string b = 2;
- }
-
- message Test3 {
- required Test1 c = 3;
- }
编码后的消息
1a 03 08 96 01
解析:
可以看到最后三个字节和之前的完全相同,并且他们前面有一个数字3 - 内嵌消息完全是和字符串(wire type = 2)一样对待。
1. 1A的二进制是"0001 1010"
2. 0001 1010的位移三位后结果是"011",表示字段的数字标签值是3,对应消息定义里面的c=3。
3. 0001 1010的后三位"010"值是2, 表示wire type为2, Length-delimited。
4. 1A后面的3表示三个字节,这和对待字符串一致。
5. 继续读取3个字节, 这是内嵌的消息Test1 c的内容, 然后按照Test1的定义继续解析这三个字节。
编码好的消息绝不会有一个可选或必需字段的多个实例。对于数值类型和字符串,如果相同的值出现多次,解析器接受它看到的最后一个值。
无论你在.proto文件中以任何顺序使用字段数字, 当消息被序列化时, 它已知的字段应该按照字段数字顺序写入。
- message := (tag value)* You can think of this as “key value”
-
- tag := (field << 3) BIT_OR wire_type, encoded as varint
- value := (varint|zigzag) for wire_type==0 |
- fixed32bit for wire_type==5 |
- fixed64bit for wire_type==1 |
- delimited for wire_type==2 |
- group_start for wire_type==3 | This is like “open parenthesis”
- group_end for wire_type==4 This is like “close parenthesis”
-
- varint := int32 | int64 | uint32 | uint64 | bool | enum, encoded as
- varints
- zigzag := sint32 | sint64, encoded as zig-zag varints
- fixed32bit := sfixed32 | fixed32 | float, encoded as 4-byte little-endian;
- memcpy of the equivalent C types (u?int32_t, float)
- fixed64bit := sfixed64 | fixed64 | double, encoded as 8-byte little-endian;
- memcpy of the equivalent C types (u?int64_t, double)
-
- delimited := size (message | string | bytes | packed), size encoded as varint
- message := valid protobuf sub-message
- string := valid UTF-8 string (often simply ASCII); max 2GB of bytes
- bytes := any sequence of 8-bit bytes; max 2GB
- packed := varint* | fixed32bit* | fixed64bit*,
- consecutive values of the type described in the protocol definition
-
- varint encoding: sets MSB of byte to 1 to indicate that there are more bytes
- zigzag encoding: sint32 and sint64 types use zigzag encoding.