Protocol-Buffers 编码过程

202 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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种。

这里看一下对应表:

image.png

从图中可以看出,很多变量类型都是使用了同种 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,那么这整个字段就直接不编码了。