protobuf数据类型如上,可分为可变长类型和不可变长类型。
Base 128 Varints
为保证传输效率,允许使用1到10个字节编码无符号64位整数,小值使用更少的字节。
MSB(most significant bit )或被称为符号位,变量当中每个字节都有一个MSB,表示后面的字节是否是该变量的一部分。
MSB 是 1 的话,则表示还有后序字节,一直读到 MSB 为 0 的字节为止。
例如,数字 1 被编码为 01
,为单个字节,所以表示为:
0000 0001
^ msb
150 被编码为 9601
, 表示为:
10010110 00000001
^ msb ^ msb
转换过程如下:
10010110 00000001 // 原始输入
0010110 0000001 // 丢掉持续位.
0000001 0010110 // 转换成大端.
00000010010110 // 连接.
128 + 16 + 4 + 2 = 150 // 被看作无符号64位整数.
消息结构
protobuf 消息是一系列键值对。即消息 = key + value,key = 字段编号(tag) + 字段类型(type)。
具体类型如上图,因为类型比较少,所以 Protocol Buffers 在编码的时候只用了 3 比特,实际传输的时候是以 (tag<<3)|type 的方式传输的。
例如:
message Foo {
int32 foo = 1;
string bar = 2;
}
Foo 的 foo 字段取值为 1
的话,则对应的编码是:0x08 0x01。foo 的类型是 int32,对应的 type 取 0。而它的 tag 又是 1,所以第一个字节是 (1<<3)|0 = 0x08,第二个字节是数字 1 的 VarInts 编码,即 0x01。
7 0 7 0
+-----+---+--------+
|00001|000|00000001|
+-----+---+--------+
tag type data
如果 Foo 的 bar 字段取值为 吕
的话,则对应的编码是:0x12 0x03 0xe5 0x90 0x95。bar 的类型是 string,对应的 type 取 2。而它的 tag 又是 2,所以第一个字节是 (2<<3)|2 = 0x12,第二个字节表示字符串的长度为 3,再后面 3 个字节是汉字吕 UTF-8 编码。
7 0 7 0 23 0
+-----+---+--------+===========+
|00010|010|00000011|0xe59095 |
+-----+---+--------+===========+
tag type length utf-8
更多数字类型
Bools and Enums
bool 类型被编码为 00
或 01
. enum类型被编码为 int32
Signed Integers
对于负数的编码如 -1,使用补码表示将占用10字节, 因为上文中 varint 是无符号的,所以不同于有符号类型,sint32和sint64 vs int32或int64,对负整数的编码不同。
sintN 使用“ZigZag”编码而不是补码,“ZigZag” 规则为正整数p被编码为2 * p,而负整数n被编码为2 * |n| - 1。因此,编码在正数和负数之间“曲折”。
例如:
Signed Original | Encoded As
0 | 0
-1 | 1
1 | 2
-2 | 3
… | …
0x7fffffff | 0xfffffffe
-0x80000000 | 0xffffffff
即对于 sint32 : (n << 1) ^ (n >> 31)
对于 sint64 : (n << 1) ^ (n >> 63)
Non-varint Numbers
double 和 fixed64 作为 I64,float 和 fixed32 作为 I32 。
Length-Delimited Records
LEN 类型有一个动态长度,由紧跟在标签后面的变量指定,标签后面跟着有效 payload。如上文的 string bar结构。
Submessages
子消息字段也使用 LEN 类型,如:
message Test1 {
optional int32 a = 1;
}
message Test3 {
optional Test1 c = 3;
}
设置 Test1 中 a 值为 150,得到编码为 1a03089601
,分割后:
1a 03 [08 96 01]
[]中的三个字节为 Test1 的编码,1a = 3 << 3 | 2 ,03 为 payload的长度。