Protocol Buffer (二)原理篇

上一篇文章简单地描述了一下Protocol Buffer的用法,懂得了上篇文章的用法之后,相信大家正常使用Protocol Buffer已经没有太大问题了,没有覆盖到的细节用法,在网上搜一下应该也不难找到。然而知其表而复知其理,知其华而复知其实,所以今天这篇文章给大家介绍一下Protocol Buffer的一些编码原理,可以用来解释为什么序列化之后的数据包比XML和JSON小那么多。

一、Varint编码
Protocol Buffer 在对数字进行编码使用的是Varint的编码方式。我们使用的int类型,一般需要占用4个字节的长度,采用Varint编码,可以用一个字节或多个字节来表示一个数字,值越小的数字占用越少的字节数。

Varint的每个byte的最高位bit用来表示后续的byte是不是也属于该数字的一部分。如果最高位为0,则表示该byte是这个数字的最后一个byte,如果最高位为1,表示后面的一个byte仍然是该数字的一部分。以无符号数为例,对于0-127,只需要一个字节表示,最高位为0,后7位表示值。如果一个数字需要多个字节去表示,这里面有个需要注意的地方:高位的数字是放在后面的字节里面的。

example:300的Varint编码为1010 1100,0000 0010,去掉两个最高位为010 1100,000 0010,将后面的字节放到前面为000 0010 010 1100,也就是100101100,换算成十进制就是300了。

对于有符号的数字,Protocol Buffer 还会采用ZigZag编码,这样无论正数还是负数,绝对值小的时候使用Varint编码占用的字节数就会更少。ZigZag编码大家从下面一张图就可以看明白原理了。

ZigZag编码.png

二、消息体编码
我们平时用JSON传递数据,Java对象序列化为JSON串之后一般会是下面这个样子。

{"code":200,"name":"magiccoder","sex":"man","index":"www.magiccoder.net"}

可能平时一直这么用,大家都已经习以为常了,但是这个对象的数据包在频繁传输的过程中,key值其实是一直重复传输的,key值仅仅是一个标志位,只要客户端和服务端约定之后采用同样的key值就可以,如果把key值直接约定为从1开始的自然数,肯定是比上面的JSON串的长度要短的。甚至我们可以这么想,把需要传输的字段值按照约定的顺序排列下来,做好字段之间的分界,就连key值都不需要了。

Protocol Buffer使用的是Tag-Length-value的形式来组织数据的,Length字段非必需。

  • Tag值有两个作用,一是标志该字段在message中是第几个值,二是标明该字段的数据类型。按照官方文档的说法,他们用Tag中的3个bit来表示数据类型。这样对于固定长度的数据字段,在反序列的时候,通过数据类型的长度就可以正确地界定和取出数据。对于Varint类型的字段,程序也可以按照Varint的编码方式将value值计算出来。下面这张图片是从官方截取的,大家可以看到一共有5种数据类型,那么就需要3个二进制去表示。Tag值采用Varints编码,如果Tag占用一个字节,1个bit用来表示MSB,3个bit用来表示数据类型,还有4个bit用来表示字段在message中的位置,最多可以表示16个字段,一般可以满足大部分需要了。


    wiretype.png
  • length字段表示可变长度的字段时用到的,属于可选字段。对于上图中type序号为2的类型,字段长度不是固定的,像大家熟知的String类型,长度变化范围太大,单靠数据类型已经没法界定它的范围了。Protocol Buffer 使用的策略是在Tag值之后用一个length字段来标志value的长度,这样也就可以正确的界定字段的范围啦。

  • 对于repeated字段和optional字段,如果没有设置,序列化的时候就会被直接忽略,不会参与消息体编码。

三、Protocol Buffer 的其他优点
笔者认为Protocol Buffer除了数据包小,序列化和反序列化速度快,还有下面两个很不错的优点:

  • 语言互通
    现在很多编程语言都已经支持Protocol Buffer 了,到笔者写这篇文章的时候,Java、C++、C#、python、Golang都已经支持了。像我们之前做通讯软件的时候一样,后端使用Golang,客户端使用Java,两边直接使用同一份proto文件描述的message,就可以自动生成通信用的类或者结构体,可以减少非常多的麻烦。客户端跟后端闹得不愉快,大部分时候都是因为通信的消息体出问题。
  • 向后兼容
    软件发布新版本的时候,经常会出现这样的情况,旧版的软件跟新的软件在通信协议方面不兼容。如果使用Protocol Buffer的话,新通信协议中添加的新字段不会影响到旧版本的软件,它会把自己不具有的字段忽略掉。

附上官方网站,上面介绍的还是很全面的,英文还不错的同学可以看一下:
https://developers.google.com/protocol-buffers/

2017年第一周,最近玩的太high了,所以过了这么久才更新这篇文章。
最后祝大家2017年身体健康、工作顺利、心想事成、笑口常开~

推荐阅读更多精彩内容