ProtoBuf格式详解

介绍protobuf编码格式。


protobuf是一种数据交换格式,又称PB编码,由Google开源,类似于Json、XML,但其内部是纯二进制格式,比Json,XML等格式要更精炼,主要用于数据的序列化和反序列化,目前官方提供了JAVA、Python、C++等多种语言的实现。


PB格式的解析依赖于消息文件,在其实现中,.proto定义了各个消息项的id值。


直观地,PB编码就是将一个结构体的内容编码成二进制流。例如一段json数据:

 "id":176,

 "age":24,

 "name":"xieyifenxi",

}


.proto文件的定义如下:

message Person { 

required int32 id = 1;

optional int32 age = 2;

required string name = 3;

}

则json数据编码成PB格式则是:

08 b0 01 10 18 1a 0a 78 69 65 79 69 66 65 6E 78 69


通常,在协议解析的过程中碰到的PB编码,是没有.proto文件的,解析的时候,只需要根据数据内容,解析出每一项内容即可,而每一项内容的含义,一般通过分析得到。


在许多APP的数据流中,都会存在protobuf编码。本文将通过对PB编码进行介绍,使大家了解如何在协议分析过程中对其进行解析。


01

数据结构


通过前面的例子,可以看到PB的数据结构就是每项数据独立编码,包含一个表示数据类型wire_type和字段序号field_number的数据头,和对应的数据段内容。即HEAD1+MSG1+HEAD2+MSG2+……


在数据头中,字段的序号field_number在整个数据结构中是唯一的,并且可以乱序、缺失和嵌套,序号是在.proto文件中定义的,协议分析中关心的意义不是很大。


数据类型wire_type则表示数据段内容是什么类型,在protobuf官网上描述了类型的含义:

https://developers.google.com/protocol-buffers/docs/encoding


常见的数据类型wire_type02,分别为Varint类型和Length-delimited变长度数据类型,掌握了这两个类型,基本上在协议解析中,处理PB编码就基本没有障碍了。Varint类型一般就是int数据,而Length-delimited变长度数据类型通常就是字符串数组等数据。


数据头中数据类型和字段序号的组合方式是:(field_number << 3) | wire_type

当然,field_number是Varint类型,需遵循Varint的编码规则。


例如,文首的例子中,id、age、name对应的编码值为:

1 <<< 3 | 0 =0x08

2 <<< 3 | 0 = 0x10

3 <<< 3 | 2 = 0x1a


数据头之后,是数据段,它包含了被编码的数据,不同类型的数据编码格式不同,后面的章节将介绍对应具体类型的编码方法。


02


Varint


Varint是一种对数字进行编码的方法,将数字编码成不定长的二进制数据,数值越小,编码后的字节越少。


编码规则如下:

每个字节的最高位表示下一字节是否仍然是编码的内容,若最高位为1,则下一字节仍然是编码的数字的一部分,若该位为0,则编码到本字节结束。每个字节的后7位,则由小端表示的数字的二进制值,在高位补0凑齐7的倍数位组成。


例如,数值345,其二进制值为 1 0101 1001,在高位补0后分成两个7位 000 0010和 101 1001,则Varint编码结果为:

1 101 1001 0 000 0010

即0xD9 0x02


对文首的例子,由于id 176的二进制值为1011 0000,每七位编码成一个字节,因此,需要用两个字节来表示:

1011 0000 0000 0001

0xB0 0x01


而age 24的二进制值为 1 1000,则只需要一个字节来表示:

0001 1000

即0x18


前面只是弄明白了int32的Varint编码,对协议解析来说,一般已经够用了,除非这个被编码的数,在取出后需要用其特定的含义来进行计算,因为在PB编码中,还考虑了对负数进行Varint编码


当我们按照同样的逻辑对负数进行Varint编码时,会发现,负数编码后占用的字节会很多,这不太合算,因此ZigZag编码在PB中被使用,使得Varint编码可以用较少的位数来对负数进行编码。


PB编码中提供了sint32和sint64类型,使用ZigZag编码,让所有的负数都使用正数表示,计算方式如下:

sint32:

(n << 1) ^ (n >> 31)

sint64:

(n << 1) ^ (n >> 63)


即:

原始值0,通过计算,得到ZigZag表示值为0;

原始值-1,通过计算,得到ZigZag表示值为1;

原始值1,通过计算,得到ZigZag表示值为2;

原始值-2,通过计算,得到ZigZag表示值为3;

原始值2147483647,通过计算,得到ZigZag表示值为4294967294;

原始值-2147483648,通过计算,得到ZigZag表示值为4294967295。

依此类推


在协议还原中,对一个Varint编码的值,想要知道它表达的是int32,还是sint32,就只有想办法找到其对应的.proto文件才可以。


03


Length-delimited


Length-delimited就是对可变长度的数据,在编码时,将长度和数据编码在一起,类似于TLV结构的LV部分,前面为数据长度,后面为由数据长度决定的数据内容,数据长度采用的是Varint编码。


例如文首的例子里,name的值为"xieyifenxi"的长度为10,则编码为:

0a 78 69 65 79 69 66 65 6E 78 69


其中,0x0a为长度值的Varint编码,之后紧接着的是值的内容。


这相当的简单。




对protobuf编码的详解就介绍到这里了,有疑问,可以联系我,或者上其官网了解。它的官网是https://developers.google.com/protocol-buffers/


640?wx_fmt=jpeg

长按进行关注。





  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值