diff --git a/protobuf/protobuf.md b/protobuf/protobuf.md index db216bc..9980d93 100644 --- a/protobuf/protobuf.md +++ b/protobuf/protobuf.md @@ -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`。 + +
| 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 |