Protobuf使用
目录
proto3的更新
- 在第一行非空白非注释行,必须写:
syntax = "proto3";
-
字段规则移除了
required
,并把optional
改名为singular
;
在proto2
中required
也是不推荐使用的。proto3
直接从语法层面上移除了required
规则。其实可以做的更彻底,把所有字段规则描述都撤销,原来的repeated
改为在类型或字段名后加一对中括号。这样是不是更简洁? -
repeated
字段默认采用packed
编码;
在proto2
中,需要明确使用[packed=true]
来为字段指定比较紧凑的packed
编码方式。 -
移除了
default
选项;
在proto2
中,可以使用default
选项为某一字段指定默认值。在proto3
中,字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。
在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。
但这样就无法区分某字段是根本没赋值,还是赋值了默认值。这在proto3
中问题不大,但在proto2
中会有问题。
比如,在更新协议的时候使用default
选项为某个字段指定了一个与原来不同的默认值,旧代码获取到的该字段的值会与新代码不一样。 -
枚举类型的第一个字段必须为 0 ;
-
移除了对分组的支持;
分组的功能完全可以用消息嵌套的方式来实现,并且更清晰。在proto2
中已经把分组语法标注为『过期』了。这次也算清理垃圾了。 -
移除了对扩展的支持,新增了
Any
类型;
Any
类型是用来替代proto2
中的扩展的。目前还在开发中。
proto2
中的扩展特性很像Swift
语言中的扩展。理解起来有点困难,使用起来更是会带来不少混乱。
相比之下,proto3
中新增的Any
类型有点像C/C++
中的void*
,好理解,使用起来逻辑也更清晰。 -
增加了
JSON
映射特性;
语言的活力来自于与时俱进。当前,JSON
的流行有其充分的理由。很多『现代化』的语言都内置了对JSON
的支持,比如Go
、PHP
等。而C++
这种看似包罗万象的学院派语言,因循守旧、故步自封,以致于现出了式微的苗头。 -
map支持
map<key_type, value_type> map_field = N;
example:
map<string, Project> projects = 3;
- 在 proto3 中,纯数字类型的 repeated 字段编码时候默认采用 packed 编码(具体原因见 Protocol Buffer 编码原理 这一章节)
定义协议格式
.proto
文件中的定义很简单:为要序列化的每个数据结构添加消息,然后为消息中的每个字段指定名称和类型。这是.proto
定义您的消息的文件addressbook.proto
。
(好的.proto
文件命名风格是:packagename.messagename.proto
)
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
该.proto
文件以包声明开头,这有助于防止不同项目之间的命名冲突。在C++
中,生成的类将放在与包名匹配的命名空间中。
每个元素上的“= 1”,“= 2”标记标识该字段在二进制编码中使用的唯一“标记”。标签号1-15需要少于一个字节来编码而不是更高的数字,因此作为优化,您可以决定将这些标签用于常用或重复的元素,将标签16和更高版本留给不太常用的可选元素。重复字段中的每个元素都需要重新编码标记号,因此重复字段特别适合此优化。
可以指定的最小字段编号为1,最大字段编号为229-1 或 536,870,911。也不能使用数字 19000 到 19999(FieldDescriptor :: kFirstReservedNumber 到 FieldDescriptor :: kLastReservedNumber),因为它们是为 Protocol Buffers实现保留的。
必须使用以下修饰符之一注释每个字段:
-
required(proto3中移除):必须提供该字段的值,否则该消息将被视为“未初始化”。如果
libprotobuf
在调试模式下编译,则序列化未初始化的消息将导致断言失败。在优化的构建中,将跳过检查并始终写入消息。但是,解析未初始化的消息将始终失败(通过false
从解析方法返回)。除此之外,必填字段的行为与可选字段完全相同。 -
optional(proto3中为singular):该字段可能已设置,也可能未设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像我们
type
在示例中为电话号码所做的那样。否则,使用系统默认值:数字类型为0,字符串为空字符串,bools
为false
。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,其中没有设置其字段。调用访问器以获取尚未显式设置的可选(或必需)字段的值始终返回该字段的默认值。 -
repeated(proto3默认采用 packed 编码):该字段可以重复任意次数(包括零)。重复值的顺序将保留在协议缓冲区中。将重复字段视为动态大小的数组。
-
proto3 中移除了default选项:字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。
编译protobuf
现在运行编译器,指定源目录(应用程序的源代码所在的位置 - 如果不提供值,则使用当前目录),目标目录(您希望生成的代码在哪里;通常相同$SRC_DIR
) ,以及你的道路.proto
。:
protoc -I = $ SRC_DIR --cpp_out = $ DST_DIR $ SRC_DIR / addressbook.proto
这里都生成到当前目录,输入
protoc -I=. --cpp_out=. .