参考官方文档:Protocol Buffers Developer Guide
1. 简介
ProtoBuf 全称 Protocol Buffers,是谷歌开源的一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储。
类比 XML、JSON 等:
| | XML | JSON | ProtoBuf | | ------ | -------------------- | ------------------ | ------------------------------------------------ | | 可读性 | 标签形式,可读性较高 | 键值对,可读性较高 | 原始格式为二进制,可读性低(.proto文件可读性较高) | | 速度 | 慢 | 中 | 高速(比JSON快 3~5倍,比XML快20~100倍) | | 体积 | 大,内容较冗余 | 中 | 小(比XML小3~10倍) |
Protoctol 的特点:
- 语言无关、平台无关:支持 Java、C++、Python、Go 等多种语言,支持多个平台
- 高效:比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
- 扩展性、兼容性好:可以更新数据结构,而不影响和破坏原有的旧程序
- 代码生成机制:可以通过代码生成代码
2. 用法
2.1. .proto 文件
ProtoBuf 通过在 .proto 文件中定义 protocol buffer message 类型,来指定如何对序列化信息进行结构化。
每一个 protocol buffer message 是一个信息的小逻辑记录。
2.2. Message 类型
2.2.1 语法(proto3)
官方英文文档:https://developers.google.com/protocol-buffers/docs/proto3
// 指定使用 proto3 语法
syntax = "proto3";
message 名称 {
[字段规则] 类型 名称 = 字段编号;
// 例如:string name = 1;
}
// 一个 .proto 文件中可以用多个 message 类型
// 注释使用 // 或 /*...*/
- 关键字
message
定义一个由多个消息字段 (field)组成的实体结构 - **消息字段(field)**包括:字段规则、类型、名称、编号
- 字段规则:singular、repeated
- 类型:标量类型、合成类型
- 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
2.2.2. 字段规则说明
- singular:字段出现 0 次或 1 次,超过 1 次
- repeated:字段可以出现任意次(包括 0 次),其中重复值的顺序会被保留
在proto3中,
repeated
数字类型的字段默认使用packed
编码。您可以在协议缓冲区编码中找到更多有关
packed
编码的信息。
2.2.3. 保留字段
使用 reserved
指定保留的字段,保留一些曾经使用过后来删除或注释掉的编号或名称,避免未来有人修改这个 .proto
文件的时候,重复使用这些编号或名称,不然如果以后万一因某些原因加载到相同 .proto
的旧版本时可能会导致出现一些数据损坏或隐蔽的bug。确保不会发生这种情况的一种方法是指定保留已删除条目的数值(或名称,名称可能导致JSON序列化问题)。如果将来有任何用户尝试使用这些标识符,则 protocol buffer 编译器会发生警告。
message Foo {
reserved 2, 15, 9 to 11; // 指定保留字段编号
reserved "foo", "bar"; // 指定保留字段名称
}
字段编号跟名称不能出现在同一行,例如错误的例子:reserved 2, 15, "foo";
2.2.4. 默认值
解析 message 时,如果编码的消息不包含特定的 singular 元素,则已解析对象中的相应字段将设置为该字段的默认值。
下列的每个类型对应的默认值:
| 类型 | 默认值 | | ---------------- | --------------------------------- | | 字符串(stirng) | 空字符串 | | bytes | 空 bytes | | 布尔值(bool) | false | | 数值 | 0 | | 枚举 | 默认是第一个定义的枚举值,必须为0 |
对于 message 类型,如果未设置字段。它的确切值取决于使用的语言。有关详细信息,请参见 generated code guide。
repeated
字段的默认值为空(生成一个相应语言的空列表)
注意:对于标量类型的 message 字段,一旦被解析,如果值是等于默认值的(例如:布尔类型字段值等于false),无法确认它的值是被显式设置的还是本来就没有设置值。例如你不应该定义 boolean 的默认值 false 作为任何行为的触发方式。也应该注意如果一个标量类型的 message 字段被设置为默认值,这个值将不会被序列化传输。
2.2.5. 例子
定义了一个包含 "person" 相关信息的 message
// 指定使用 proto3 语法,不指定的话,默认会用 proto2 语法,现在基本上都用 proto3
syntax = "proto3";
// 定义一个名称为 SearchRequest 的消息类型
message SearchRequest {
string query = 1; // 定义类型为 string,名称为 query ,编号为 1 的字段(field)
int32 page_number = 2;
int32 result_per_page = 3;
}
2.3. 使用 .proto 可以生成什么?
| 语言 | 生成文件 | 描述 |
| ----------- | -------------------- | ------------------------------------------------------------ |
| C++ | .h 和 .cc | 生成的文件包含 .proto
文件中描述的每种 message 类型对应的类 |
| Java | .java | 每个 message 类型生成一个 .java 文件(类),以及用于创建 message 类实例的特殊 Builder 类 |
| Python | | Python有点不同,Python编译器为.proto文件中的每个message类型生成一个含有静态描述符的模块,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。 |
| Go | .pb.go | 每个 message 类型在 .pb.go 中会声明对应的类型 |
| Ruby | .rb | 生成一个包含所有 mesaage 类型的 Ruby 模块文件 |
| Objective-C | pbobjc.h 和 pbobjc.m | 文件中描述的每种 message 类型都有一个类。 |
| C# | .cs | 文件中描述的每种 message 类型都有一个类。 |
| Dart | .pb.dart | 文件中描述的每种 message 类型都有一个类。 |
2.4. 枚举(Enum)
2.4.1. 使用
当你定义一个 message 类型时,可能希望一个字段是从一个预定义的列表里面取值,此时就可以使用枚举。
例如:想在 SearchRequest
中添加一个 corpus
字段,这个字段是值可能是 UNIVERSAL
, WEB
, IMAGES
, LOCAL
, NEWS
, PRODUCTS
或 VIDEO
。可以在 message 中定义一个 enum
以及对应的每个可能值的常量在实现这个操作。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
每个 enum 的第一个常量必须映射为 0,并且这个映射为 0 的常量必须存在:
- 必须有一个零值,这个我们可以把这个 0 作为默认值
- 这个零值必须作为第一个常量元素,为了兼容proto2,枚举类的第一个值总是默认值
当需要分配两个相同的值给不同的常量时,可以使用别名。
使用别名的时候,必须将 allow_alias
选项设置为 true
,否则编译器遇到别名的时候会产生一个错误信息。
// option allow_alias = true 设置后,允许 STARTED、RUNNING 映射同样的值
message MyMessage {
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
枚举器常量必须在 32 位整型值的范围内。因为enum
值传输使用 varint 编码,使用负值效率不高,因此不推荐使用。如上例所示,可以在 一个 message 定义的内部或外部定义枚举——这些枚举可以在 .proto 文件中的任何 message 定义里重用。当然也可以在一个 message 中声明一个枚举类型,而在另一个不同的 message 中使用它——采用 MessageType.EnumType
的语法格式。
当使用 protocol buffer 编译器编译一个使用了 enum
的 .proto 文件时,生成的代码中将有一个对应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说),后者被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。
注意:生成的代码可能受到特定于枚举器数量的语言的限制(一种语言的下限为几千)。请查看您计划使用的语言的限制。
在反序列化的过程中,无法识别的枚举值会被保存在消息中,如何表示该值取决于语言。在支持具有超出指定范围的值的开放式枚举类型的语言(例如C ++和Go)中,未知的枚举值仅存储为其基础整数表示形式。在具有封闭枚举类型的语言(例如Java)中,使用枚举中的一个类型来表示未识别的值,并且可以使用特殊的访问器访问基础整数。无论哪种情况,如果 message 已序列化,则无法识别的值仍将与 message 一起序列化。
更多关于如何在你的应用程序的中使用枚举的信息,请查看所选择语言的 generated code guide
2.4.2. 保留字段
使用 reserved
指定保留的字段,保留一些曾经使用过后来删除或注释掉的编号或名称,避免未来有人修改这个 .proto
文件的时候,重复使用这些编号或名称,不然如果以后万一因某些原因加载到相同 .proto
的旧版本时可能会导致出现一些数据损坏或隐蔽的bug。确保不会发生这种情况的一种方法是指定保留已删除条目的数值(或名称,名称可能导致JSON序列化问题)。如果将来有任何用户尝试使用这些标识符,则 protocol buffer 编译器会发生警告。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
可以使用 max 关键字指定保留的数值范围达到最大可能值。
不能在同一个 reserverd 语句中混合使用字段名和数值。
2.5. 使用 message 作为另一个 message 的字段类型
2.5.1. 用法
字段类型可以使用另一个 message 类型。
例如:在 SearchResponse
message 中包含 Result
// 定义一个 SearchResponse message 类型
// 将 Result 作为 SearchResponse 的字段类型
message SearchResponse {
repeated Result results = 1; // 指定字段类型为 Result
}
// 在同一 .proto 文件中,定义 Result message 类型
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
2.5.2. 引入
上例是在同一文件中使用另一个 message 作为字段类型,同样也可以通过引入,使用其他 .proto 文件中定义的 message。
import "myproject/other_protos.proto";
默认只能使用直接引入的 .proto 文件中的定义。但有时可能需要将 .proto 文件移动到新的位置,此时可以在旧的位置放置一个虚拟的 .proto 文件,使用 import public 语句转发所有引入的 .proto 文件,而不是直接移动 .proto 文件并修改每个引用的路径。
引入的 .proto 文件如果包含 import public ,则会传递转发这个 import public 引入的依赖。
例如:
new.proto
// 新的位置的 .proto 文件
// 所有原来 .proto 的内容都转移到此文件
old.proto
// 这个是原来被引用的就文件
import public "new.proto"; // 引入新位置的文件
import "other.proto";
client.proto 其他引用了 old.proto 的文件
import "old.proto"; // 引入旧位置的 .proto 文件
// 可以使用 new.proto 和 old.proto 中定义的内容,但不能使用 other.proto 中定义的内容
2.6.
3. 标量类型列表
https://developers.google.com/protocol-buffers/docs/proto3#scalar