示例
假如定义一个如下的protobuf类型
message Person {
required string user_name = 1;
optional int64 favorite_number = 2;
repeated string interests = 3;
}
将其赋值为:
user_name : "Martin"
favorite_number : 1337
interests:"daydreaming", "hacking"
则生成的数据解析如下:
数据格式
从上图可以看到整个数据的解析过程。从这个过程中可以看出,实际上。protobuf的数据格式是tag+length+value模式(TLV模式)。如下:
protobuf数据就是这样一个长条形的序列。再继续细分的话,可以看到tag实际上是有2部分组成。
tag= tag<<3 | type
tag的高5位为tag,tag更容易理解的话就是数据的编号,上例中user_name 的tag=1,说明读到tag=1,后面跟的数据就是user_name。 favorite_number 和interests 都是同理,只不过其编号分别是2 和3,这个tag在定义.proto文件时就会固定。解析的话,就是根据这个tag来表明tag后面的数据含义。
继续看下type是干啥。在protobuf中,谷歌定义了几种数据类,type实际上是一个数据类型的集合。
ID | Name | Used For |
---|---|---|
0 | VARINT | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | I64 | fixed64, sfixed64, double |
2 | LEN | string, bytes, embedded messages, packed repeated fields |
3 | SGROUP | group start (deprecated) |
4 | EGROUP | group end (deprecated) |
5 | I32 | fixed32, sfixed32, float |
其中3和4已经废弃掉了实际上没有用到。
示例中第一个字节是0x0a,对应的tag=1,type=2。当type=2的时候,tag后面跟的数据就是length。当type为其他值的时候,tag后面的数据就是value。
解析tag=1数据
继续来看tag=1的数据,
0a 06 4d 61 72 74 69 6e
第二个字节就是长度为6字节,表示tag=1的数据有6字节长度。其value=4d 61 72 74 69 6e ,转换成ascII就是Martin。至此tag=1的数据解析完毕。
解析tag=2数据
继续来看tag=2的数据
10 b9 0a
0x10解析出来后tag=2 ,type=0。此时b9就是数据而不是数据长度。一直tag=2是favorite_number 数据,为1337,所以b9 0a 解析出来应该是1337(0x539)。而1337的十六进制表示0x539,可见这个编码并不是实际的16进制数。
对于数字类型的,采用的是Varints编码模式。这种编码模式采用的是小端模式。其每个字节实际上是拆分成了2个部分。
最高bit(bit7)表面下一个字节是否是数据的一部分。
bit7=1,下一个字节数据中还是包含的数据。
bit7=0,数据只有一个字节
bit6-0 是数据。
具体来看b9 0a这两个数据
b9二进制为1 0111001 ,其bit7=1说明下一个字节0a也是也是favorite_number 的一部分
0a二进制为0 0001010 ,其中bit7=0,说明favorite_number 的数据到此结束。
由于采用的是小端模式。所以最终的数据是0001010 0111001,对应的16进制是539,十进制为1337,这样就解析出来了favorite_number 的值。编码的过程就是这个逆过程。
参考:
https://www.jianshu.com/p/73c9ed3a4877
https://protobuf.dev/programming-guides/encoding/
https://gitee.com/ljango/ddia/blob/master/ch4.md#thrift%E4%B8%8Eprotocol-buffers