开发学院,分享开发教程和最新动态

Protobuf 编码(1)

  本文档描述了协议缓冲消息的二进制格式。在应用程序中使用Protocol Buffer不需要理解这一点,但是了解不同的Protocol Buffer格式如何影响编码消息的大小会非常有用。

一条简单的信息

  假设您有以下非常简单的消息定义:

message Test1 {
  optional int32 a = 1;
}

  在应用程序中,您创建一个Test1消息,并将设置为150。然后将消息序列化为输出流。如果您能够检查编码的消息,您会看到三个字节:

08 96 01

  到目前为止,数字如此之小——但这意味着什么呢?继续阅读...

Base 128 Varints

  要理解您的简单Protocol Buffer编码,您首先需要理解varints。Varints是一种使用一个或多个字节序列化整数的方法。较小的数字占用较小的字节数。

  变量中的每个字节,除了最后一个字节,都设置了最高有效位( msb ),这表明还会有更多的字节。每个字节的低7位用于存储7位组中数字的二进制补码表示,最低有效组优先。

  例如,这里是数字1——它是一个字节,所以msb没有设置:

0000 0001

  这里是300,这有点复杂:

1010 1100 0000 0010

  你怎么知道这是300?首先从每个字节中删除msb,因为这只是为了告诉我们是否已经到达数字的末尾(如您所见,它设置在第一个字节中,因为varint中有一个以上的字节) :

 1010 1100 0000 0010
→ 010 1100  000 0010

  颠倒两组7位,因为正如您所记得的,变量首先存储具有最低有效组的数字。然后将它们连接起来,得到最终值:

000 0010  010 1100
→  000 0010 ++ 010 1100
→  100101100
→  256 + 32 + 8 + 4 = 300

消息结构

  如您所知,协议缓冲消息是一系列键值对。消息的二进制版本仅使用字段的编号作为密钥—每个字段的名称和声明类型只能在解码端通过引用消息类型的定义(即.proto文件)来确定。

  当消息被编码时,密钥和值被连接成字节流。当消息被解码时,解析器需要能够跳过它不能识别的字段。这样,新字段可以添加到邮件中,而不会破坏不知道它们的旧程序。为此,线格式消息中每一对的“键”实际上是两个值—来自.proto文件的字段号,加上一个线类型,该线类型提供刚好足够的信息来找到以下值的长度。在大多数语言实现中,这个键被称为标签。

  可用的消息类型如下:

类型意义用于
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bit fixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (已废弃)
4End groupgroups (已废弃)
532-bitfixed32, sfixed32, float

  流式消息中的每个密钥都与值(字段编号< < 3) | wire_type )不同,换句话说,数字的最后三位存储了wire类型。


  现在让我们再看一遍我们的简单例子。您现在知道了,数据流中的第一个数字总是一个不同的varint 键,这里是08,或者(去掉msb ) :

000 1000

  您取最后三位得到wire类型(0),然后右移三位得到字段号(1)。现在你知道字段号是1,下面的值是一个varint。使用上一节中不同的解码知识,您可以看到接下来的两个字节存储了值150。

96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (丢弃msb并反转7位组)
       → 10010110
       → 128 + 16 + 4 + 2 = 150

  未完待续...