doc: 阅读protobuf encoding文档
This commit is contained in:
@@ -40,6 +40,17 @@
|
||||
- [optimize\_for (file option)](#optimize_for-file-option)
|
||||
- [deprecated (field option)](#deprecated-field-option)
|
||||
- [生成代码](#生成代码)
|
||||
- [Encoding](#encoding)
|
||||
- [Simple Message](#simple-message)
|
||||
- [Base 128 varints](#base-128-varints)
|
||||
- [varint编码原理](#varint编码原理)
|
||||
- [消息结构](#消息结构)
|
||||
- [wire type](#wire-type)
|
||||
- [More Integer Types](#more-integer-types)
|
||||
- [Boolean \& Enum](#boolean--enum)
|
||||
- [Length-Delimited Records](#length-delimited-records)
|
||||
- [Sub messages](#sub-messages)
|
||||
- [Optional \& Repeated](#optional--repeated)
|
||||
|
||||
|
||||
# protobuf
|
||||
@@ -464,4 +475,124 @@ protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_ou
|
||||
在指定输入时,也可以指定多个proto文件。
|
||||
|
||||
|
||||
## Encoding
|
||||
默认,protobuf会将消息序列化为wire format,wire format定义了如何将消息发送到wire中,并定义了消息占用的空间大小。
|
||||
|
||||
### Simple Message
|
||||
如下是一个简单的消息定义示例:
|
||||
```proto
|
||||
message Test1 {
|
||||
optional int32 a = 1;
|
||||
}
|
||||
```
|
||||
在application code中,可以创建Test1消息实例并将a设置为150,之后将消息序列化到outputstream中。`被序列化之后消息的hex内容为`:
|
||||
```
|
||||
08 96 01
|
||||
```
|
||||
如果使用protoscope tool来转储这些字节,可以得到`1:150`的结果。
|
||||
|
||||
### Base 128 varints
|
||||
可变宽度整数(varints)为wire format的基础。通过varints,可以将unsigned 64bit整数编码为1~10个字节。
|
||||
|
||||
> varint可以对固定长度8字节的64bit unsigned整数进行编码,当64bit整数越小,varint花费的字节数越少。
|
||||
>
|
||||
> varint中每个字节的载荷为7位(取值范围为0~127),故而对64bit整数进行编码时,最多可能需要花费10个字节。`(7 * 9 = 63 < 64)`
|
||||
>
|
||||
> 而对于7位载荷能够代表的数字,例如1,则varint只需要1个字节即能对其进行编码。
|
||||
|
||||
#### varint编码原理
|
||||
对于varint中的每个字节,都含有一个`continuation bit`,代表下一字节是否为varint的一部分,`continuation bit`是字节的`MSB`。
|
||||
|
||||
> 故而,在varint编码产生的结果中,最后一个字节的MSB都是0,而其他字节的MSB都是1,这样可以对varint进行分隔。
|
||||
|
||||
构成varint的每个字节,除了MSB之外的7bit构成了载荷,将所有字节的7bit拼接在一起即是varint代表的整数。
|
||||
|
||||
例如,整数1,其编码为varint后的长度为1字节,内容为`01`,其中`MSB`为0,代表varint只由当前字节构成,7位载荷为`0000001`,代表该varint的值为1。
|
||||
|
||||
对于整数150,其16进制代表为`0x96`,一个字节能表示的最大数为127,故而150需要两个字节来表示,表示的载荷为`1001 0110`,将其分割为7位并由两个字节来表示之后,在大端环境下,编码后字节内容为`1 0000001 0 0010110`,即`0x8116`;在小端环境下,编码后字节内容为`1 0010110 0 0000001`,即`0x9601`。
|
||||
|
||||
> 网络传输默认使用大端字节序,故而网络传输字节内容为`0x9601`
|
||||
|
||||
### 消息结构
|
||||
protocol buffer message由一系列的key/value pairs构成,message的二进制数据,将`field number`作为key,field的类型以及field name只有在decode时参照message type的定义才能得知。
|
||||
|
||||
当消息被编码后,每个key/value pair都会被转化为一条记录,记录中包含`field number, wire type, payload`。其中,`wire type`会告知parser后续payload的大小。
|
||||
|
||||
> 对于old parser,通过wire type可以得知payload的大小,故而old parser可以跳过unknown field。
|
||||
|
||||
#### wire type
|
||||
wire type故而被称为`tag-length-value`,即`TLV`。
|
||||
|
||||
有6中wire type,`varint,i64, len, sgroup, egroup, i32`。
|
||||
|
||||
<table><thead><tr><th>ID</th><th>Name</th><th>Used For</th></tr></thead><tbody><tr><td>0</td><td>VARINT</td><td>int32, int64, uint32, uint64, sint32, sint64, bool, enum</td></tr><tr><td>1</td><td>I64</td><td>fixed64, sfixed64, double</td></tr><tr><td>2</td><td>LEN</td><td>string, bytes, embedded messages, packed repeated fields</td></tr><tr><td>3</td><td>SGROUP</td><td>group start (deprecated)</td></tr><tr><td>4</td><td>EGROUP</td><td>group end (deprecated)</td></tr><tr><td>5</td><td>I32</td><td>fixed32, sfixed32, float</td></tr></tbody></table>
|
||||
|
||||
对于一条记录(field key/value pair), 其`tag`被编码为了一个varint,被编码值的计算公式如下:
|
||||
```
|
||||
(field_number << 3) | wire_type
|
||||
```
|
||||
故而,在对varint类型的tag进行decode操作后,其结果最低三位代表`wire type`,其他部分代表field number。
|
||||
|
||||
故而,可以stream中永远以`varint`数字开头,代表field的`tag`,例如,stream中的第一个字节为`08`
|
||||
```
|
||||
0000 1000
|
||||
```
|
||||
其去掉符号位后,载荷为`0001 000`,后三位代表wire type,值为`0`的wire type代表varint,即field value的类型为varint。前4位代表field number,故而field number的值为`0001`,即`1`。
|
||||
|
||||
故而,`08`tag 代表field number为1,并且field value的类型为varing的field。
|
||||
|
||||
### More Integer Types
|
||||
#### Boolean & Enum
|
||||
bool类型和enum类型的编码方式和`int32`一致,bool值通常会被编码为`00`或`01`。
|
||||
|
||||
#### Length-Delimited Records
|
||||
`length prefixed`为wire format另一个要点。wire type中值为2的类型为`LEN`,其在`tag`后有一个varint类型的动态长度,动态长度之后跟随载荷。
|
||||
|
||||
示例如下:
|
||||
```proto
|
||||
message Test2 {
|
||||
optional string b = 2;
|
||||
}
|
||||
```
|
||||
如果新建一个`Test2`的实例,并且将`b`设置为`testing`,那么其编码后的结果为
|
||||
```
|
||||
12 07 [74 65 73 74 69 6e 67]
|
||||
```
|
||||
解析如下:
|
||||
- 第一个varint值`12`代表tag,其载荷`0010 010`代表field number为`2`,并且wire type为`2`,即`LEN`。
|
||||
- wire type为`LEN`代表tag后跟随一个varint表示field值的长度,而`07`其载荷表示的值为7,代表field value的长度为7,`testing`字符串长度正好为7
|
||||
- 字符串`testing`的utf8编码为`0x74657374696e67`,其正好和后续内容一致
|
||||
|
||||
#### Sub messages
|
||||
对于sub messages类型的记录,其仍使用`LEN` wire type,在`tag`和`length varint`之后,跟随的是sub message编码之后的二进制内容。
|
||||
|
||||
#### Optional & Repeated
|
||||
对于otpional场景,编码时,如果field没有被设置,只需要跳过其即可。
|
||||
|
||||
对于repeated场景,普通(not packed)repeated fields会为field中的每一个元素单独发送一个record,示例如下:
|
||||
|
||||
```proto
|
||||
message Test4 {
|
||||
optional string d = 4;
|
||||
repeated int32 e = 5;
|
||||
}
|
||||
```
|
||||
如果构造一个Test4实例,d为`hello`并且e为`1, 2, 3`,那么,其可以按照如下方式被编码:
|
||||
```
|
||||
4: {"hello"}
|
||||
5: 1
|
||||
5: 2
|
||||
5: 3
|
||||
```
|
||||
|
||||
其中,e的多条record顺序并不需要在一起,可以乱序排放,例如
|
||||
```
|
||||
5: 1
|
||||
5: 2
|
||||
4: {"hello"}
|
||||
5: 3
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user