protocol buffers的编码原理

protocol buffers使用二进制传输格式传递消息,因此相比于xml,json来说要轻便很多。

 

示例:假设定义了一个Message

message Test1 {
  required int32 a = 1;
}

实际使用的时候将a设置为150,然后将其序列化到输出流,查看编码后的message,可以看到如下3个byte

08 96 01

 

解析:

上述三个字节实际分为两部分: 08  96 01。第一部分(08)包含了message成员变量的field number(a=1)和变量类型(Varint),第二部分(96 01)为a的实际值150。

这里面涉及几个概念:

  Varint:这个可以理解为可变长的int类型,数值越小使用的byte越少;

  field number和type:protocol buffer消息为一系列的key-value对。二进制版本的消息使用field number作为key。

当接收到一个message时,解析器可以忽略无法识别的字段,通过这样的方式,也可以在不影响老功能的前提下添加新的字段。 通信格式下的key实际包含2个值:.proto文件中的field number,和通信类型。通信类型如下 

TypeMeaningUsed For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

message流中的key类型为varint,计算方式为:(field_number << 3) | wire_type ,即后三位保存了通信类型

上述第一个字节为08,转化为二进制为0000 1000,没个varint的第一个比特位为MSB位,置位表示后续还有字节。去掉MSB位后为

000 1000

后三位表示类型,值为0,表示类型为Varint;右移三位获取tag值为1(即message中设置的a = 1)

下面获取消息值150,注意:字节顺序为大端序

96 01 = 1001 0110  0000 0001
       → 000 0001  ++  001 0110 (drop the msb and reverse the groups of 7 bits)
       → 10010110
       → 128 + 16 + 4 + 2 = 150

以上介绍的时varint的编码方式,下面介绍一下其他类型的编码

Signed integer

int32和int64的实际类型都是varint,当它表示负数的时候,为10个固定字节长度的值,效率比较低。可以使用sint32和sint64来表示有符号的数值,它采用ZigZag编码,编码对应关系如下,实际就是把负数从0开始做了扩展。

Signed OriginalEncoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

Non-Varint Numbers

非varint的值比较简单,double和fixed64的类型为1,表示64位固定长度的值;类似地,float和fixed32类型为5,表示固定32为长度的值,这两种情况下以小端序存储

Strings

类型为2,假设创建message如下,

message Test2 {
  required string b = 2;
}

实际消息b=“testing”

12 07 74 65 73 74 69 6e 67

首字节为特殊字节:0001 0010,去除msb位:001 0010,后三位->10表示类型2,右移三位->10表示tag 2;07表示长度为7,74 65 73 74 69 6e 67为"testing"的值。

Embedded Messages

 假设定义嵌入的message如下:

message Test3 {
  required Test1 c = 3;
}

设置Test1.c=150,获得的结果如下,可以看到后三个字节跟上述的一致

1a 03 08 96 01

Packed Repeated Fields

proto2中使用repeated field需要启用特殊选项[packed=true],在proto3中,默认启用packed。如果packed repeated field中包含0个元素,则它不会出现在被解析的message中。

message Test4 {
  repeated int32 d = 4 [packed=true];
}

编码如下:

22        // tag (field number 4, wire type 2) ->0010 0,010
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942)

只有使用了原始数据类型(如32-bit或64-bit的varint)的repeated fields才能称之为"packed"

 

可以看到string,message,repeated field是有长度字段的,而varint由每个字节的msb位表示一个varint是否有后续字节

 proto的类型定义如下:

.proto说明C++JavaPythonGoRubyC#PHP
double   double double float float64 Float double float
float   float float float float32 Float float float
int32 使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint32 int32 int int int32 Fixnum or Bignum (as required) int integer
int64 使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint64 int64 long int/long int64 Bignum long integer/string
uint32 使用变长编码 uint32 int int/long uint32 Fixnum or Bignum (as required) uint integer
uint64 使用变长编码 uint64 long int/long uint64 Bignum ulong integer/string
sint32 使用变长编码,带符号的int类型,对负数编码比int32高效 int32 int int int32 Fixnum or Bignum (as required) int integer
sint64 使用变长编码,带符号的int类型,对负数编码比int64高效 int64 long int/long int64 Bignum long integer/string
fixed32 4字节编码, 如果变量经常大于228228 的话,会比uint32高效 uint32 int int int32 Fixnum or Bignum (as required) uint integer
fixed64 8字节编码, 如果变量经常大于256256 的话,会比uint64高效 uint64 long int/long uint64 Bignum ulong integer/string
sfixed32 4字节编码 int32 int int int32 Fixnum or Bignum (as required) int integer
sfixed64 8字节编码 int64 long int/long int64 Bignum long integer/string
bool   bool boolean bool bool TrueClass/FalseClass bool boolean
string 必须包含utf-8编码或者7-bit ASCII text string String str/unicode string String (UTF-8) string string
bytes 任意的字节序列 string ByteString str []byte String (ASCII-8BIT) ByteString string

 

参考:Encoding

posted @ 2018-05-07 21:19  charlieroro  阅读(2509)  评论(0编辑  收藏  举报