一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情。
Protocol Buffers 简介
这是一个很有效的序列化和反序列化协议,Google开发。
有点像json,但是编码后的体积小,编码效率高。
一般拥有这些好处的序列化协议,基本上是都是二进制的。
需要提前定义消息字段和类型
这一点不像json,json完全可以从信息直接推断字段的类型和字段的值。
举个例子:
{
"name":"xiaoming",
"age":11
}
这一段json,传给你了,展示在你面前,你很容易就看出,有两个字段:
- name,字符串类型,值是"xiaoming"
- age, number 类型,值是 11
不用提前说,你收到了,就自动可以推断。
我们看看Protocol Buffers是怎么做的,首先你需要准备一个.proto文件:
message Person {
string name = 1;
int32 age = 2;
}
里面需要定义一个message,然后把字段写在里面。还要精准的标记字段的类型。
这个类型在编码和解码时都非常有用,如果缺失这个类型,就算你收到了一串序列化之后的信息,你也无法反序列化出真正的信息。下面就来讲讲这个过程:
编码之定位
我们注意到,刚才的proto文件中,每一个字段后面跟了 = 数字
- name = 1
- age = 2
后面的这个数字,不是值,是定位用的,就是说编码后的字节,是按照这个数字来进行定位。
name = 1代表,字节序列的第一部分是name
age = 2代表,字节序列的第二部分是age
我们从这里可以看到,如果有更多的字段,只要不打乱先前的定位,那么确实可以随时往里面添加新的字段,而不用破坏老的字段。
编码之类型
这里面类型有两个概念:
- 书写proto文件时给变量规定的类型 (变量类型)
- 序列化时,编码类型(wire type)
wire type 有6种,而变量类型大大超过6种。
这里看一下对应表:
从图中可以看出,很多变量类型都是使用了同种 wire type。
比如说,int32,int64 就都使用了 Varint 这种 wire type。
字段编码的排布
对于每一个字段来说, 编码之后,都是如下排布:
[定位和wire type] [........][........]……
中括号表示一个字节,每个字段的第一个字节,表明了这个字段的索引位置和wire type。
假设索引位置是1,wire type 是0,那么第一个字节就是这样:
[00001000]
首先,wire type 是最低三位->[000] 就是 0
其次,定位就是,其余部分右移三位->[00000001] 就是 1
多个字段的排布如下:
[定位和wire type] [........][........]……
[定位和wire type] [........][........]……
[定位和wire type] [........][........]……
[定位和wire type] [........][........]……
[定位和wire type] [........][........]……
...
...
...
一个挨着一个,具体的分界,就需要用 wire type了。
具体wire type的分界
如果wire type是0,也就是Varint,那么是这样规定界限的:
最高位如果是1,说明,下一个字节依然还是这个Varint:
[定位和wire type] [1.......][0.......]
看上面的第二个字节,最高位是1,说明后面的一个字节还是属于改字段,
第三个字节,最高位是0,说明下个字节就是另一个字段。
不同的 wire type 有不同的分界规则,这个需要一个一个的去看,比如说字符串类型,就会直接把字符串长度放在前面。
定位的用处
我们看上面,其实不用定位也行,直接按照顺序来解码不就行了,为什么要把定位放在第一个字节里面。
那是因为,对于有的类型来说,如果是默认值,那么就无需编码,直接省略这个字段。
比如说,wire type 是 Varint,如果值是0,那么这整个字段就直接不编码了。