摘要: 这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示: 在 .proto 文件中定义消息格式 使用 protocol buffer 编译器 使用 C++ protocol buffer API 读写消息 这不是一个关于在 C++ 中使用 protocol buffers 的全面指南。
这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示:
- 在
.proto
文件中定义消息格式 - 使用 protocol buffer 编译器
- 使用 C++ protocol buffer API 读写消息
这不是一个关于在 C++ 中使用 protocol buffers 的全面指南。要获取更详细的信息,请参考 Protocol Buffer Language Guide 和 Encoding Reference。
为什么使用 Protocol Buffers
我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字、ID、邮件地址和联系电话。
如何序列化和获取结构化的数据?这里有几种解决方案:
- 以二进制形式发送/接收原生的内存数据结构。通常,这是一种脆弱的方法,因为接收/读取代码必须基于完全相同的内存布局、大小端等环境进行编译。同时,当文件增加时,原始格式数据会随着与该格式相关的软件而迅速扩散,这将导致很难扩展文件格式。
- 你可以创造一种 ad-hoc 方法,将数据项编码为一个字符串——比如将 4 个整数编码为
12:3:-23:67
。虽然它需要编写一次性的编码和解码代码且解码需要耗费一点运行时成本,但这是一种简单灵活的方法。这最适合编码非常简单的数据。 - 序列化数据为 XML。这种方法是非常吸引人的,因为 XML 是一种适合人阅读的格式,并且有为许多语言开发的库。如果你想与其他程序和项目共享数据,这可能是一种不错的选择。然而,众所周知,XML 是空间密集型的,且在编码和解码时,它对程序会造成巨大的性能损失。同时,使用 XML DOM 树被认为比操作一个类的简单字段更加复杂。
Protocol buffers 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 Protocol buffers,你需要写一个 .proto
说明,用于描述你所希望存储的数据结构。利用 .proto
文件,protocol buffer 编译器可以创建一个类,用于实现对高效的二进制格式的 protocol buffer 数据的自动化编码和解码。产生的类提供了构造 protocol buffer 的字段的 getters 和 setters,并且作为一个单元来处理读写 protocol buffer 的细节。重要的是,protocol buffer 格式支持格式的扩展,代码仍然可以读取以旧格式编码的数据。
在哪可以找到示例代码
示例代码被包含于源代码包,位于“examples”文件夹。可在这里下载代码。
定义你的协议格式
为了创建自己的地址簿应用程序,你需要从 .proto
开始。.proto
文件中的定义很简单:为你所需要序列化的每个数据结构添加一个消息message,然后为消息中的每一个字段指定一个名字和类型。这里是定义你消息的.proto
文件 addressbook.proto
。
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
如你所见,其语法类似于 C++ 或 Java。我们开始看看文件的每一部分内容做了什么。
.proto
文件以一个 package
声明开始,这可以避免不同项目的命名冲突。在 C++,你生成的类会被置于与package
名字一样的命名空间。
下一步,你需要定义消息message。消息只是一个包含一系列类型字段的集合。大多标准的简单数据类型是可以作为字段类型的,包括 bool
、int32
、float
、double
和 string
。你也可以通过使用其他消息类型作为字段类型,将更多的数据结构添加到你的消息中——在以上的示例,Person
消息包含了 PhoneNumber
消息,同时AddressBook
消息包含 Person
消息。你甚至可以定义嵌套在其他消息内的消息类型——如你所见,PhoneNumber
类型定义于 Person
内部。如果你想要其中某一个字段的值是预定义值列表中的某个值,你也可以定义 enum
类型——这儿你可以指定一个电话号码是 MOBILE
、HOME
或 WORK
中的某一个。
每一个元素上的 = 1
、= 2
标记确定了用于二进制编码的唯一“标签”tag。标签数字 1-15 的编码比更大的数字少需要一个字节,因此作为一种优化,你可以将这些标签用于经常使用的元素或 repeated
元素,剩下 16 以及更高的标签用于非经常使用的元素或 optional
元素。每一个 repeated
字段的元素需要重新编码标签数字,因此 repeated
字段适合于使用这种优化手段。
每一个字段必须使用下面的修饰符加以标注: