Files
rikako-note/protobuf/protobuf.md
2025-03-03 00:15:49 +08:00

599 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [protobuf](#protobuf)
- [language guideproto3](#language-guideproto3)
- [定义message type](#定义message-type)
- [Assign Field Numbers](#assign-field-numbers)
- [重复使用filed number的后果](#重复使用filed-number的后果)
- [指定字段基数](#指定字段基数)
- [Singular](#singular)
- [repeated](#repeated)
- [map](#map)
- [Message Type Files Always have Field Presence](#message-type-files-always-have-field-presence)
- [well-formed messages](#well-formed-messages)
- [在相同`.proto`中定义多个message type](#在相同proto中定义多个message-type)
- [删除Fields](#删除fields)
- [reversed field number](#reversed-field-number)
- [reversed field names](#reversed-field-names)
- [what generated from `.proto`](#what-generated-from-proto)
- [Scalar Value Types](#scalar-value-types)
- [default field values](#default-field-values)
- [enumerations](#enumerations)
- [enum value alias](#enum-value-alias)
- [修改message Type需要遵循的原则](#修改message-type需要遵循的原则)
- [Unknown Fields](#unknown-fields)
- [unknown fields丢失](#unknown-fields丢失)
- [Any](#any)
- [OneOf](#oneof)
- [OneOf Feature](#oneof-feature)
- [向后兼容问题](#向后兼容问题)
- [Maps](#maps)
- [map feature](#map-feature)
- [向后兼容性](#向后兼容性)
- [package](#package)
- [Service In Rpc System](#service-in-rpc-system)
- [gRPC](#grpc)
- [json](#json)
- [Options](#options)
- [消息级别](#消息级别)
- [java\_package (file option)](#java_package-file-option)
- [java\_outer\_class\_name (file option)](#java_outer_class_name-file-option)
- [java\_multiple\_files (file option)](#java_multiple_files-file-option)
- [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
## language guideproto3
### 定义message type
如下为一个定义search request message format的示例
```proto
synatx="proto3"
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
```
上述示例含义如下:
- `synatx = "proto3"`:
- 代表当前protobuf的language版本为`proto3`
- 如果没有指定`syntax`那么protocol buffer compiler默认会假设在使用`proto2`
- `Search Request`消息定义了3个fields每个field都代表`希望包含在message中的一部分数据`
### Assign Field Numbers
可以为message中的每个field定义一个整数范围为`[1,536,870,911]`,并有如下约束
- message中所有field的给定数字必须唯一
- field number `[19,000, 19,999]`是为`Protocol Buffer`实现保留的如果使用这些数字protocol buffer compiler将会报错
一旦消息类型被使用后field number就不能被改变field number代表message wire format中的field。
如果对field的field number进行了修改代表删除旧的field并且新建一个相同类型的field。
filed number不应该被重用。
对于`频繁被设置`的fields应该将其的field number设置为`[1,15]`。在wire format中field number的值越小占用空间越小。
> 例如,`[1,15]`在编码时只占用1字节而`[16, 2047]`则会占用2字节。
### 重复使用filed number的后果
如果重复使用field number将会造成解码wire-format message的二义性。
> 对于protobuf wire format其在编码和解码过程中`fields的定义`必须一致。
field number被限制为`29bit`故而field number的最大值为`536870911`
### 指定字段基数
在protobuf协议中field可以为如下的集中类型
#### Singular
在proto3中有两种singular field
- `optional`(推荐使用): 一个optional field可能有如下两种状态
- 如果optional field值被设置那么其将会被序列化到`wire`
- 如果optional field值未被设置那么该field将会返回一个默认值并且其不会被序列化到wire中
- `implict`(不推荐使用):一个隐式字段没有显式基数标签,并且行为如下:
- 如果field为一个message type那么其行为和`optional`相同
- 如果field不是message那么其有两种状态
- 如果field被设置为非默认值non-zero其会被序列化到wire中
- 如果field被设置为默认值那么其不会被序列化到wire中
> 相比于`implict`,更推荐使用`optional`,使用`optional`能更好与proto2相兼容
> `optional`和`implicit`的区别是如果scalar field被设置为默认值在`optional`场景下其会被序列化到wire中而`implicit`则不会对其进行序列化
#### repeated
代表该field可以在消息中出现0次或多次消息出现的顺序也将被维护
#### map
代表field为成对的键值对
### Message Type Files Always have Field Presence
在proto3中message-type field永远都存在field presence。故而对于message-type field添加`optional`修饰符并不会改变该field的field presence。
例如,如下示例中定义的`Message2``Message3`对所有的语言都会生成相同的code并且在binary json、text format格式下如下两种定义的数据展示都不会有任何区别
```proto
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
```
### well-formed messages
`well-formed`来修饰`protobuf message`其代表被序列化或反序列化的bytes。在对bytes进行转化时protoc parser将会校验是否`proto定义文件`是可转化的。
对于singular field其可以在`wire-format` bytes中出现多次parser会接收该输入但是在转化过程中只有field的最后一次出现才有效。
### 在相同`.proto`中定义多个message type
可以在相同`.proto`文件中定义多个message type示例如下所示
```proto
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
```
> 应尽可能的在每个proto文件中包含较少的message type定义在同一proto问文件中包含过多message type可能会造成依赖的膨胀。
### 删除Fields
当不再需要某个field并且所有对该field的references都被从client code中移除时可以从message type definition中移除该field。`但是该field对应的field number必须被reserved防止field number后续被重用`
该field对应的field name也应该被reserved以允许`按json或text-format进行编码`的消息你能偶被正常的转换。
#### reversed field number
在将field注释或删除时将来使用者可能仍会对field number进行重用。为了避免该问题可以将被删除field的field number添加到`reversed`列表中,示例如下:
```proto
message Foo {
reversed 2, 15, 9 to 11;
}
```
> 上述示例中,`9 to 11`代表`9,10,11`
#### reversed field names
对被删除field的field name通常是安全的除非使用TextProc或json的编码格式在使用这些格式时field name也会参与序列化。为了避免该问题可以将`deleted field name`添加到`reversed`列表中。
reversed names只会影响protoc compiler的行为并不会对rumtime behavior造成影响但是存在一个例外
- 在parse过程中TextProto实现会丢弃`reversed`中包含的未知fields而不会抛出异常
- runtime json parse过程不会受到reversed names影响
使用reversed names示例如下
```proto
message Foo {
reversed 2, 15, 9 to 11;
reversed "foo", "bar";
}
```
> 上述示例中将field numbers和field names分为了两个`reversed`语句,实际上,可以在同一行`reversed`语句中包含它们
### what generated from `.proto`
当针对`.proto`文件运行`protocol buffer compiler`时compiler将会生成`所选中编程语言`对应的`和message type进行交互的代码`,生成的交互代码包括如下部分:
- getting and setting field values
- serialize message to outputstream
- parse message from input stream
对于常用变成语言,其生成文件内容如下:
- java:
- 对于java其会为每个message type生成其对应的class
- 除了clas外还会生成对应的Builder类用于生成class实例
- go:
- 对于go其会为每个message type生成`.pb.go`文件
### Scalar Value Types
一个scalar message field可以是如下类型
<table><tbody><tr><th>Proto Type</th><th>Notes</th></tr><tr><td>double</td><td></td></tr><tr><td>float</td><td></td></tr><tr><td>int32</td><td>Uses variable-length encoding. Inefficient for encoding negative
numbers if your field is likely to have negative values, use sint32
instead.</td></tr><tr><td>int64</td><td>Uses variable-length encoding. Inefficient for encoding negative
numbers if your field is likely to have negative values, use sint64
instead.</td></tr><tr><td>uint32</td><td>Uses variable-length encoding.</td></tr><tr><td>uint64</td><td>Uses variable-length encoding.</td></tr><tr><td>sint32</td><td>Uses variable-length encoding. Signed int value. These more
efficiently encode negative numbers than regular int32s.</td></tr><tr><td>sint64</td><td>Uses variable-length encoding. Signed int value. These more
efficiently encode negative numbers than regular int64s.</td></tr><tr><td>fixed32</td><td>Always four bytes. More efficient than uint32 if values are often
greater than 2<sup>28</sup>.</td></tr><tr><td>fixed64</td><td>Always eight bytes. More efficient than uint64 if values are often
greater than 2<sup>56</sup>.</td></tr><tr><td>sfixed32</td><td>Always four bytes.</td></tr><tr><td>sfixed64</td><td>Always eight bytes.</td></tr><tr><td>bool</td><td></td></tr><tr><td>string</td><td>A string must always contain UTF-8 encoded or 7-bit ASCII text, and cannot
be longer than 2<sup>32</sup>.</td></tr><tr><td>bytes</td><td>May contain any arbitrary sequence of bytes no longer than 2<sup>32</sup>.</td></tr></tbody></table>
上述scalar type在各个编程语言中对应的类型如下所示
<table style="width:100%;overflow-x:scroll"><tbody><tr><th>Proto Type</th><th>C++ Type</th><th>Java/Kotlin Type<sup>[1]</sup></th><th>Python Type<sup>[3]</sup></th><th>Go Type</th><th>Ruby Type</th><th>C# Type</th><th>PHP Type</th><th>Dart Type</th><th>Rust Type</th></tr><tr><td>double</td><td>double</td><td>double</td><td>float</td><td>float64</td><td>Float</td><td>double</td><td>float</td><td>double</td><td>f64</td></tr><tr><td>float</td><td>float</td><td>float</td><td>float</td><td>float32</td><td>Float</td><td>float</td><td>float</td><td>double</td><td>f32</td></tr><tr><td>int32</td><td>int32_t</td><td>int</td><td>int</td><td>int32</td><td>Fixnum or Bignum (as required)</td><td>int</td><td>integer</td><td>int</td><td>i32</td></tr><tr><td>int64</td><td>int64_t</td><td>long</td><td>int/long<sup>[4]</sup></td><td>int64</td><td>Bignum</td><td>long</td><td>integer/string<sup>[6]</sup></td><td>Int64</td><td>i64</td></tr><tr><td>uint32</td><td>uint32_t</td><td>int<sup>[2]</sup></td><td>int/long<sup>[4]</sup></td><td>uint32</td><td>Fixnum or Bignum (as required)</td><td>uint</td><td>integer</td><td>int</td><td>u32</td></tr><tr><td>uint64</td><td>uint64_t</td><td>long<sup>[2]</sup></td><td>int/long<sup>[4]</sup></td><td>uint64</td><td>Bignum</td><td>ulong</td><td>integer/string<sup>[6]</sup></td><td>Int64</td><td>u64</td></tr><tr><td>sint32</td><td>int32_t</td><td>int</td><td>int</td><td>int32</td><td>Fixnum or Bignum (as required)</td><td>int</td><td>integer</td><td>int</td><td>i32</td></tr><tr><td>sint64</td><td>int64_t</td><td>long</td><td>int/long<sup>[4]</sup></td><td>int64</td><td>Bignum</td><td>long</td><td>integer/string<sup>[6]</sup></td><td>Int64</td><td>i64</td></tr><tr><td>fixed32</td><td>uint32_t</td><td>int<sup>[2]</sup></td><td>int/long<sup>[4]</sup></td><td>uint32</td><td>Fixnum or Bignum (as required)</td><td>uint</td><td>integer</td><td>int</td><td>u32</td></tr><tr><td>fixed64</td><td>uint64_t</td><td>long<sup>[2]</sup></td><td>int/long<sup>[4]</sup></td><td>uint64</td><td>Bignum</td><td>ulong</td><td>integer/string<sup>[6]</sup></td><td>Int64</td><td>u64</td></tr><tr><td>sfixed32</td><td>int32_t</td><td>int</td><td>int</td><td>int32</td><td>Fixnum or Bignum (as required)</td><td>int</td><td>integer</td><td>int</td><td>i32</td></tr><tr><td>sfixed64</td><td>int64_t</td><td>long</td><td>int/long<sup>[4]</sup></td><td>int64</td><td>Bignum</td><td>long</td><td>integer/string<sup>[6]</sup></td><td>Int64</td><td>i64</td></tr><tr><td>bool</td><td>bool</td><td>boolean</td><td>bool</td><td>bool</td><td>TrueClass/FalseClass</td><td>bool</td><td>boolean</td><td>bool</td><td>bool</td></tr><tr><td>string</td><td>string</td><td>String</td><td>str/unicode<sup>[5]</sup></td><td>string</td><td>String (UTF-8)</td><td>string</td><td>string</td><td>String</td><td>ProtoString</td></tr><tr><td>bytes</td><td>string</td><td>ByteString</td><td>str (Python 2), bytes (Python 3)</td><td>[]byte</td><td>String (ASCII-8BIT)</td><td>ByteString</td><td>string</td><td>List<int></int></td><td>ProtoBytes</td></tr></tbody></table>
### default field values
当message执行反序列化操作时如果encoded message bytes中并不包含指定的field那么对反序列化后的对象访问field时将会返回一个默认值。
各种类型的默认值都不一样:
- 对于`string`类型,默认值为空字符串
- 对于`bytes`默认值为empty bytes
- 对于`bool`类型默认值为bool
- 对于numeric types默认值为0
- 对于message fields默认值为该field没有被设置
- 对于enum默认值为`first defined enum value`
对于repeated fields其默认值为`empty`empty list
对于map fields其默认值为emtpy(empty map)。
> #### implicit-presence
> 对于implicit presence scalar fields, 当消息被反序列化后,没有方法区分`该field是被显式设置为default value`还是`该field根本未被设置`。
### enumerations
在proto中定义枚举的示例如下
```proto
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
```
在proto3中`first value defined in enum`其值必须为0并且其名称必须为`{ENUM_TYPE_NAME}_UNSPECIFIED`或`{ENUM_TYPE_NAME}_UNKNOWN`。
#### enum value alias
当开启`allow_alias` option时允许为相同的枚举值指定不同的枚举项。在反序列化时所有的alias值都有效但是只有第一个会被用于反序列化。
enum alias示例如下
```proto
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message.
ENAA_FINISHED = 2;
}
```
在使用枚举时可以在一个message type中定义枚举然后在另一个message type中使用枚举语法如下` _MessageType_._EnumType_`。
### 修改message Type需要遵循的原则
如果旧的message type不再满足需求想要添加新的field且在修改message type后仍想和旧代码保持兼容则必须要遵守如下原则
- 不要修改field number
- 在添加新field后使用旧`meesage type`序列化到的消息格式仍然能被新的`message type`反序列化。同样的,新的`message type`序列化的消息同样能被旧的`message type`定义反序列化
- 即是,若`service A`和`service B`通过message type进行通信`service A`为调用方而`service B`为被调用方,如果`service B`修改了message type定义向message中添加了field但是`serivce A`仍然使用的是旧的message定义那么`service A`使用旧的message type定义序列化的数据仍然能被`service B`反序列化
- 同样的,`service B`使用新的message type定义序列化的新消息数据仍然能被`service A`反序列化,`对于旧的消息定义,其会忽略新添加的字段`
- field可以被删除但是被删除的field其field number不能被重用。
- `int32, uint32, int64, uint64, bool`这些类型的值都是兼容的代表可以将field的类型从一个修改为另一个并且不会打破向前或向后兼容。
- `sint32和sint64`能够彼此兼容,但是和其他数值类型不相兼容
- `string和bytes`能够互相兼容但要求bytes为有效的utf8字符串
- 嵌套的消息能够和bytes相互兼容但是要求bytes内容为序列化后的message实例
- `fixed32和sfixed32`相互兼容,`fixed64`和`sfixed64`相互兼容
- 对于`string`, `bytes`message fields, singular和`repeated`能够相互兼容。
- 假如被序列化的数据中包含repeated field并且client期望该field为singluar那么在反序列化时
- 若field为primitive typeclient会取repeated field的最后一个值
- 如果field为message type那么client会对所有的field element进行merge操作
- `enum`和`int32, uint32, int64, uint64`能够互相兼容
### Unknown Fields
如果被序列化的数据中包含parser无法识别的fields时其被称为unknown fields。例如old parser针对new sender发送的数据进行反序列化时如果new sender发送的数据中包含new field那么new
field即为unknown field。
proto3会对unknown fields进行保存并在序列化和反序列化时包含它们该行为和proto2一致。
#### unknown fields丢失
一些行为可能会造成unknown fields丢失示例如下
- 将消息序列化为json
- 遍历消息中的field并将其注入给新的message
为了避免unknown fields的丢失遵循如下规则
- 使用binary格式在数据交换时避免使用text-format
- 使用message-oriented api来拷贝消息例如`CopyFrom`或`MergeFrom`不要使用field-by-field的拷贝方式
### Any
any的使用类似于泛型允许在使用嵌套类型时无需声明其`.proto`定义,`Any`将会包含如下内容
- 任意被序列化为bytes的消息
- 一个唯一标识消息类型的url用于对消息的反序列化
为了使用Any类型需要`import google/protobuf/any.proto`,示例如下:
```proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
```
对于给定消息类型的默认type url为`type.googleapis.com/_packagename_._messagename_.`。
### OneOf
如果消息中包含多个singluar fields并且在同一时刻最多只能有一个被设置则可以使用OneOf特性。
OneOf fields类似于optional fields但是所有oneof fields都保存在oneof共享内存中在同一时间最多只能有一个field被设置。`对任一oneof member进行设置都会自动清空其他oneof members`。
可以通过`cause()或WhichoneOf()`方法来得知哪一个field被设置。
oneof使用如下所示
```protobuf
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
```
在使用oneof field时可以向oneof中添加任意类型的field除了`repeated`和`map`。
#### OneOf Feature
- 当parser对数据进行转换时如果存在多个oneof member被设置那么只有最后一个oneof member才会被使用
- oneof不能为repeated
- 反射api对oneof field也适用
#### 向后兼容问题
在向oneof中添加field时应该要注意如果oneof的值返回`None/NOT_SET`,其可能代表`oneof没有被设置`或`其有可能被设置但是设置的member不包含在旧版本的消息类型中`。
### Maps
如果想要创建一个map作为data definition的一部分可以使用如下语法
```proto
map<key_type, value_type> map_field = N;
```
上述示例中,`key_type`可以是任意整数类型或string类型。`value_type`可以是除了`map`外的任何类型。
创建示例如下所示;
```proto
map<string, Project> projects = 3;
```
#### map feature
- map fields 不能被`repeated`修饰
- map中值的遍历顺序以及值在format中的顺序是未定义的
- 在merge或反序列化过程中如果存在多个相同的key那么最后出现的key将会被使用
#### 向后兼容性
map synatx声明等价于如下声明
```proto
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
```
故而不支持maps的protocol buffer实现也能够接收map数据。
### package
可以在`.proto`文件中指定一个package name用于避免message type冲突示例如下
```proto
package foo.bar;
message Open { ... }
```
```proto
message Foo {
...
foo.bar.Open open = 1;
...
}
```
### Service In Rpc System
如果想要在rpc系统中使用message type类型可以在proto文件中定义rpc service interface。protocol buffer compiler将会生成service interface code和stub示例如下
```proto
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
```
#### gRPC
与protocol buffer一起使用的最简单的rpc系统为`gRPC`。其是语言和平台中立的,其能够直接通过`.proto`文件产生rpc代码。
### json
`binary wire format`为protobuf的首选格式但protobuf支持json编码规范。
### Options
`.proto`文件中的声明可以添加options。options并不会改变声明的含义但是会影响特定上下文下对声明的处理。
#### 消息级别
- 部分option是文件级别的其应当被写在文件作用域的最上方而不应该在message type、enum、service之内
- 部分option则是message级别的其应该被写在消息中
- 部分option是field级别的其应该被写在field 定义之内
常用options如下
#### java_package (file option)
`java_package` option代表生成classes的package name。如果没有指定该选项会默认使用`proto`文件的package。
使用示例如下:
```proto
option java_package = "com.example.foo";
```
#### java_outer_class_name (file option)
生成class文件的outer classname。如果该选项没有指定默认为`proto`文件的文件名,使用示例如下:
```proto
option java_outer_classname = "Ponycopter";
```
#### java_multiple_files (file option)
如果该选项被指定为false那么所有`.proto`文件产生的内容都会被嵌套在outer class中。
如果选项被指定为true那么会为每个message type单独生成一个java文件。
该选项默认为false使用示例如下
```proto
option java_multiple_files = true;
```
#### optimize_for (file option)
该选项可以被设置为`SPEED, CODE_SIZE, LIFE_RUNTIME`其将会影响java和c++生成器:
- SPEED: SPEED为默认值protocol buffer compiler将会生成序列化、反序列化、执行其他常用操作的代码。生成的代码是高度优化的
- CODE_SIZE: protocol buffer compiler将会生成最小的类代码类代码中依赖共享、反射等操作来实现序列化、反序列化以及其他常用操作指定该选项后生成的代码长度要比SPEED小得多但是操作可能会更慢。
- LIFE_RUNTIME
使用示例如下所示:
```proto
option optimize_for = CODE_SIZE;
```
#### deprecated (field option)
如果该option为true代表该field已经被废弃并不应该被新代码使用。在java中其代表生成的代码中field将会被标注为`@Deprecated`。
使用示例如下所示:
```proto
int32 old_field = 6 [deprecated = true];
```
### 生成代码
在安装完protocol buffer compiler后可以通过运行`protoc`命令来生成类文件,示例如下:
```bash
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
```
其中IMPORT_PATH代表当使用import指令时查找`.proto`文件的路径,如果省略,将会使用当前目录。如果要指定多个目录时,可以传递多个`--proto_path`选项。
在指定输入时也可以指定多个proto文件。
## Encoding
默认protobuf会将消息序列化为wire formatwire 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`作为keyfield的类型以及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`varinti64, 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 packedrepeated 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
```