Protocol Buffers 使用了一种中立和平台无关的语言来定义数据结构。然后基于该数据结构定义生成不同编程语言操作代码来实现数据的序列化和反序列化数据,从而使得不同编程语言和不同平台之间能够进行数据交互。
Protocol Buffers 目前主要有 Proto2 和 Proto3 两个版本,Proto3 保持了与 Proto2 的一定程度的兼容性。但是,某些 Proto2 特性在 Proto3 中不再支持。
Document:https://protobuf.dev/
1. Proto2
Document:https://protobuf.dev/programming-guides/proto2
1.1 标量类型
Document:https://protobuf.dev/programming-guides/proto3/#scalar
案例:创建 employee.proto 文件,定义 Employee 消息:
syntax = "proto2"; // required 表示必填字段 // optional 表示可选字段 // 建议:一般不建议在此处通过 required 来约束字段必填,应该通过程序来控制 message Employee { // 姓名 required string name = 1; // 年龄 optional int32 age = 2; // 薪资 optional double salary = 3; // 性别 optional bool gender = 4; }
注意:当创建数据字段时,每个字段都需要指定一个唯一的位置编号,用于区分不同的字段。
1.2 复杂类型
在 message 中也可以定义较为复杂的类型,例如:
- 枚举类型,枚举类型用于定义一组命名常量。
- 嵌套消息,在一个消息中嵌套另一个消息。
- 重复字段,表示一个字段可以出现零次或多次,可以理解为动态数组
- 映射字段,表示键值对的集合。
案例:创建 developer.proto 文件,定义 Developer 消息:
syntax = "proto2"; // 枚举项需要设置唯一的值 enum Week { Mon = 2; Tue = 3; Wed = 4; Thu = 5; Fri = 6; Sat = 7; Sun = 8; } message Other { optional int32 v1 = 1; optional int32 v2 = 2; } message Developer { // 名称 required string name = 1; // 休息日 optional Week rest = 2; // 邮箱 repeated string emails = 3; // 电话 map<string, int32> teles = 4; // 其他 required Other other = 5; }
注意:message 允许嵌套定义,即:message 中嵌套另外一个 message。
2. import 用法
import
指令允许在 Protobuf 中将一个 .proto 文件中的定义引入到另一个 .proto 文件中,以便在后者中使用前者中定义的消息类型、枚举类型等。
这种组织方式有助于管理大型的 .proto 定义,使得可以将不同的消息类型分别定义在不同的文件中,并通过 import
将它们组合在一起。import
指令通常位于文件顶部,其语法类似于其他编程语言中的导入或引用语句。
案例:创建 specialist.proto 文件,定义 Specialist 消息:
syntax = "proto2"; // 导入 employee.proto 文件 import "proto/employee.proto"; message Specialist { optional string name = 1; optional int32 age = 2; // 使用其他 proto 定义的数据类型 optional Employee emp = 3; }
3. package 用法
在 Protobuf 中,package
关键字用于定义一个命名空间,用于组织和管理定义的消息类型。它有助于避免命名冲突,并使得代码更加清晰和易于维护。
common1.proto 文件内容如下:
syntax = "proto2"; package com.common1; message Information { optional string info_content1 = 1; optional string info_content2 = 2; }
common2.proto 文件内容如下:
syntax = "proto2"; package com.common2; message Information { optional int32 info_id1 = 1; optional int32 info_id2 = 2; }
common.proto
import "proto/common1.proto"; import "proto/common2.proto"; message Message { // 通过包名访问 Message 类型 optional com.common1.Information info1 = 1; optional com.common2.Information info2 = 2; }
2. Proto3
Proto3 相对于 Proto2 更加简洁和直观,去掉了一些复杂的特性,比如 required 等。这样做有助于减少了语法上的混淆和歧义。
- 在 Proto3 中,所有字段都是 optional。在序列化时,未设置的字段也不会被包含在序列化后的数据中。
- 在 Proto3 中,枚举的第一个值需要设置为 0 值,这是因为枚举类型的默认值是 0 值,并且不能指定其他值。
syntax = "proto3"; import "proto3/developer.proto"; enum Week { // 第一个枚举项的值必须为 0,在 proto2 中没有此限制,这是因为对于枚举类型默认值是 0 // 后续的枚举项值可以是任意的、不重复的 Mon = 0; Tue = 3; Wed = 4; Thu = 5; Fri = 6; Sat = 7; Sun = 8; } // proto3 中不可以设置字段为 required,必备字段通过程序来控制 // 字段是 optional message Employee { // 此处 optional 可省略 string name = 1; repeated int32 list = 2; map<string, int32> map = 3; Developer developer = 4; }