Protocol Buffers 的编码

警告
本文最后更新于 2023-03-03,文中内容可能已过时。
1
2
3
message HelloRequest {
  string name = 1;
}

https://image.linux88.com/2023/03/03/852107b428296e0a1422dc1f4cf521d1.png

一个 Protocol Buffers 的消息,由 3 部分组成 字段编号 field index线路类型 wire type有效负载 payload。这种类型的方案叫做 TLV。上图中的 Value 就是 payload,承载用户的数据。

https://image.linux88.com/2023/03/03/b792ff66768fcda70715545b5959667c.png

上图中的 Tag 是由 field indexwire 组成的:

  • field index:PB message 中定义的 1,2,3 序号。
  • wire:告诉解析器它之后的 payload 有多大。

wire 有 6 种类型:VARINTI64LENSGROUPEGROUPI32

ID Name Used For
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 I64 fixed64, sfixed64, double
2 LEN string, bytes, embedded messages, packed repeated fields
3 SGROUP group start (deprecated)
4 EGROUP group end (deprecated)
5 I32 fixed32, sfixed32, float

Tag 的计算方式:tag = (field_number << 3) | wire_type

1
2
3
4
// 测试 PB Test1
message Test1 {
  int32 a = 1;
}

This is the most significant bit (MSB) of the byte (sometimes also called the sign bit). The lower 7 bits are a payload。

这是字节的最高有效位 (MSB)(有时也称为符号位)。低 7 位是 payload;如果一个字节的 第 8 位是 0,表示这个字节是整数的最后一个字节。如果 第 8 位是 1,则还有 后续字节

当 A 是 127 的时候,payload 是 01111111

1
2
3
4
5
6
func main() {
	t1 := &v1.Test1{A: 127}
	b, _ := proto.Marshal(t1)
	// [00001000 01111111], [8 127], 087F
	fmt.Printf("%08b, %d, %X", b, b, b)
}
  • Tag(field numberwire):(0000 0001 « 3) | 0000 0000 = 000 1000 = 8。
  • payload:01111111:为 0 表示最后一个字节。

当 A 是 150 的时候,payload 是 10010110 00000001

1
2
3
4
5
6
func main() {
	t1 := &v1.Test1{A: 150}
	b, _ := proto.Marshal(t1)
	// [00001000 10010110 00000001], [8 150 1], 089601
	fmt.Printf("%08b, %d, %X", b, b, b)
}
  • payload 10010110:为 1 表示后续还有字节。

varint 大于 127 后 payload 计算方式:

1
2
3
4
5
10010110 00000001  		// 原始输入
 0010110  0000001  		// 删除`sign bit`
 0000001  0010110  		// 按小端序排列
 10010110          		// 连接
 128 + 16 + 4 + 2 = 150	// 解释为整数

负数的话用到了 ZigZag 编码,将有符号整数转化为无符号整数,便于在编码中进行压缩和传输。

1
2
3
4
5
6
func main() {
	t1 := &v1.Test1{A: -2}
	b, _ := proto.Marshal(t1)
	// [00001000 11111110 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000001], [8 254 255 255 255 255 255 255 255 255 1], 08FEFFFFFFFFFFFFFFFF01
	fmt.Printf("%08b, %d, %X", b, b, b)
}
  • Tag:(0000 0001 « 3) | 0000 0000 = 8。
  • payload:11111110 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000001
1
2
3
4
// 测试 PB Test2
message Test2 {
  string b = 2;
}

LEN wire 类型,消息是由 Tag + 一个varint指定长度 + payload 组成。

1
2
3
4
5
6
func main() {
	t1 := &v1.Test2{B: "123456"}
	b, _ := proto.Marshal(t1)
	// [00010010 00000110 00110001 00110010 00110011 00110100 00110101 00110110], [18 6 49 50 51 52 53 54], 1206313233343536
	fmt.Printf("%08b, %d, %X", b, b, b)
}
  • Tag: (0000 0010 « 3) | 0000 0010 = 0001 0010 = 18
  • 一个表示长度的 varint:6 表示后面字符串的长度。
  • payload:“123456” 的 UTF-8 字符编码是 49 50 51 52 53 54
1
2
3
4
5
6
func main() {
	t1 := &v1.Test2{B: "我"}
	b, _ := proto.Marshal(t1)
	// [00010010 00000011 11100110 10001000 10010001], [18 3 230 136 145], 1203E68891
	fmt.Printf("%08b, %d, %X", b, b, b)
}
  • 长度 varint:3。
  • payload:“我”的 UTF-8 字符编码 E68891

I64 是 8 个字节, I32 是 4 个字节。

1
2
3
message Test10 {
  float d = 1;
}

1.5 = 1 + 0.5 = 2^0 + 2^-1 二进制表示 0 01111111 10000000000000000000000,IEEE 754 1符号位 + 8指数位 + 23位数位

1
2
3
4
5
6
func main() {
	t1 := &v1.Test10{D: 1.5}
	b, _ := proto.Marshal(t1)
	// [00001101 00000000 00000000 11000000 00111111], [13 0 0 192 63], 0D0000C03F
	fmt.Printf("%08b, %d, %X\n", b, b, b)
}
  • Tag:(0000 0001 « 3) | 0000 0101 = 13
  • payload: 00000000 00000000 11000000 00111111

Protocol Buffers Encoding

gRPC: Up and Running