找回密码
 立即注册
查看: 241|回复: 0

Protobuf编码原理

[复制链接]
发表于 2022-9-1 08:01 | 显示全部楼层 |阅读模式
前言:

引用自同事的一次技术分享,内容简单易懂,例子生动,不信你们看不懂。
1. message布局

message TestA {
  uint64 aa = 1;
}以上面message为例子,message序列化的内容为

  • 字段信息(tag)

    • field_num:字段的序列号,也就是【uint64 aa =1;】中的这个1
    • writetype:字段类型,也就是【uint64 aa =1;】中的uint64

  • 值(value)

    • value:字段的值,运行时开发者赋值的内容

字段信息和对应值组成键值对(key-value),key也叫tag,tag是按varint编码,value是根据tag里面的writetype来决定用什么方式编码;
字段

下图为类型为0(蓝色),序列号为1(绿色)的字段系信息




  • 标志位:第1位为标志位(msb),0表示后续没有字节,1表示后续还有字节要读;
  • 序列号编码:第2-5位为序列编码(field_num),序列号的长度是可变的,取决于msb,上图中序列号0001表示1,也就是field_num=1,当field_num超过16的时候,序列号编码就会按字节增长,例如field_num=16时,整个字段的编码就变为1000 0001 0000 0000,加黑的部分提取出来00000010000=16
  • 类型编码:第6-8位为类型编码,writetype, 不管字段信息占多少个字节长度,最后3位都表示字段类。writetype

    • 0: varint, 表示后面的value采用base 128varint读取,表示类型包括int32、int64、uin32、uint64、sint32、sint64、bool、enum
    • 1: 64_bit,  表示固定读取64bit,表示类型包括fixed64、sfixed64、double
    • 2: length-delimited,后面有一个length,表示接下来要读取length个字节;表示类型包括string、bytes、嵌套类型、repeated字段
    • 3-4:作废;
    • 5:32_bit, 表示固定读取3bit,表示类型包括fixed32、sfixed32、float

ps:只有类型2是 tag+length+value三个值,其他类型都是tag+value;
      如果message里面嵌套message,其实本质上也是一段tag-value格式的二进制数据;
      如果是repeated模式,在 proto2 中默认是没有加上 [packed=true] ,所以是tag-value ... tag-value的格式;  proto3版本默认是加上的,就是是tag-value-value-...-value模式,省去了后面的tag;


下图值为1(绿色)



  • 标志位:第1位为标志位(msb),0表示后续没有字节,1表示后续还有字节要。与字段信息的msb一个含义
  • 字段值编码:第2-8位为值编码,上图0000001表示当前值为1,如果字段值大小超过了2的7次方,例如值=2的7次方,那么整个值信息变为10000 0001 0000 0000,把加黑的部分提出出来000000010000000=2的7次方
2. Signed Integers 编码

varint的最高位一般用来表示是否继续读取字节,那有符号数怎么处理?
Protobuf 的内部将 int32 类型的负数转换为 uint64 来处理,转换后的 uint64 数值的高位全为 1, 相当于是一个 8 字节的很大的无符号数,
而int64由于是有符号数的原因,因此最高位的1也不能丢弃,
因此采用 Base128 Varints 编码后将恒定占用 10 个字节的空间,因为 ceil(64/7) = 10;
protobuf定义了sint32,sint64这两种类型,采用zigzag编码;
zigzag映射函数为:
Zigzag(n) = (n << 1) ^ (n >> 31), n 为 sint32 时
Zigzag(n) = (n << 1) ^ (n >> 63), n 为 sint64 时
按照这种方法,-1 将会被编码成 1,1 将会被编码成 2,-2 会被编码成 3,如下表所示:

说白了,就是按规则把负数映射成正数;
3. 例子详解

message TestA {
  uint64 aa = 1;
}

message TestPb {
  uint32 a = 1;
  double b = 2;
  float  c = 3;
  repeated uint64 d = 4;
  string e = 16;
  TestA f = 5;
  int32 g = 6;
  sint32 h = 7;
}赋值:
ta := &TestPb{
   A: 1,
   B: 1.1,
   C: 1.1,
   D: []uint64{256, 1, 2},
   E: "abc",
   F: &testpb.TestA{
      Aa: 2,
   },
   G: -1,
   H: -1,
}
taa, _ := proto.Marshal(ta)
fmt.Println("marshal TestPb:", taa)
打印Marshal后的二进制:
[8 1 17 154 153 153 153 153 153 241 63 29 205 204 140 63 34 4 128 2 1 2 42 2 8 2 48 255 255 255 255 255 255 255 255 255 1 56 1 130 1 3 97 98 99]
解析:

  • uint32 a = 1,赋值1
    8: 0 0001 000, msb 0 没有后续字节, 0001对应field_num=1, 000对应writetype为0,采用varint;
    1: 0 0000001, msb 0, 0000001对应value为1;
  • double b = 2,赋值1.1
    17: 0 0010 001, msb 0, 0010对应field_num=2, 001对应writetype为1,后续读取64_bit;
    154 153 153 153 153 153 241 63:对应double 1.1在内存的二进制;
  • float  c = 3,赋值1.1
    29: 0 0011 101, msb 0, 0011对应field_nun=3, 101对应writetype为5,后续读取32_bit;
    205 204 140 63:对应float1.1在内存的二进制;
  • repeated uint64 d = 4,赋值256, 1, 2
    34: 0 0100 010,msb0, 0100对应field_num = 4, 010对应writetype为2,需要读取一个length长度的字节;
    4: length,后续读取4字节;
    128 2:1000 0000,0000 0010,去掉msb后转化: 000 0010, 000 0000(小端模式,需要逆序) 等于256
    1 2:0000 0001, 0000 0010 对应1, 2
  • TestA f = 5,赋值&testpb.TestA{Aa: 2}
    42:0 0101 010,msb 0,0101对应field_num = 5, 010对应writetype为2,需要读取一个length长度字节;
    2:length,后续读取2个字节;
    8:0 0001 000,msb0, 0001对应field_nun =1, 000对应writetype为0,采用varint;
    2:0000 0010对应2;
  • int32 g = 6,赋值-1
    48:0011 0000,msb 0,  0110对应field_num=6, 000对应writetype为0,采用vatint;
    255 255 255 255 255 255 255 255 255 1 (注:Protobuf 的内部将 int32 类型的负数转换为 uint64 来处理,所以相当于是8字节) 解码后对应为-1,详情参考上面2.2节;
  • sint32 h=7, 赋值-1
    56: 0011 1000,msb 0, 0111对应field_num=7, 000对应writetype为0,采用vatint;
    1:因为这里是sint32类型,所以需要用zigzag解码,1解码后为-1;
  • string e = 16,赋值“abc”
    130 1: 1000 0010, 0000 0001, msb为1,所以需要读取后续一个字节,去掉msb逆序后为:000 0001 000 0010,1 000 0对应field_num=16, 010对应writetype为2,需要读取一个length长度的字节;
    3: length,后续读取3个字节;
    97 98 99: 对应字符 “abc”

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-2-22 18:29 , Processed in 0.092909 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表