protobuf 的编码规则
protobuf采用了 TLV(tag-length-value) 编码格式对数据进行编码。每个字段都有唯一的 tag 值,它是字段的唯一标识。length 表示 value 数据的长度,length 不是必须的,对于固定长度的 value,是没有 length 的。value 是数据本身的内容。
对于 tag 值,它有 field_number 和 wire_type 两部分组成。field_number 就是在前面的 message 中我们给每个字段的编号,wire_type 表示类型,是固定长度还是变长的。 wire_type 当前有0到5一共6个值,所以用3个 bit 就可以表示这6个值。tag 结构如下图。
wire_type 值如下表, 其中3和4已经废弃,我们只需要关心剩下的4种。对于 Varint 编码数据,不需要存储字节长度 length。这种情况下,TLV 编码格式退化成 TV 编码。对于64-bit和32-bit也不需要 length,因为type值已经表明了长度是8字节还是4字节。
手动解码实例
想要解码protobuf 编码的字符串需要先知道定义的数据格式。现有message定义如下:
message TopMessage{
int messageType
message CallStates {
int type
int state
}
}
现在接受到的字符串:0x08 0x04 0x2A 0x04 0x08 0x02 0x10 0x01
0x08 -> 0000 1000 后三位为000即type为0,说明0x08 0x04是一个filed,0x04就是数据值,对应到TopMessage的结构,message type为 04;
Tips: Varint是一种可变长的编码方式,bit的最高位为1时表示后面也是数据的一部分。所以小于128的值都只需要一个bit,数值越大,需要的位数越多。举个例子,假如接受到的是0x08 0x96 0x01:
0x96 -> 1001 0110,最高位是1,说明后面的01也是数据的一部分。01 -> 0000 0001 最高位为0,说明后面没有数据了。数据传输使用的是小端模式,0x96是数据的低位,0x01是数据的高位,把bit的最高位去掉后进行组合 : 000 0001 001 0110 -> 1001 0110 -> 即message type为 150
0x2A -> 0010 1010 后三位为010即type为2,说明2A后面的04表示数据长度,即0x2A 0x04 0x08 0x02 0x10 0x01是一个filed,对应到TopMessage的结构就是CallStates整个;
继续解析
0x08 -> 0000 1000 后三位为000即type为0,说明0x08 0x02是一个filed,对应到CallStates中就是type为02;
0x10 -> 0001 0000 后三位为000即type为0,说明0x10 0x01是一个filed,对应到CallStates中就是state为01;