Protocol Buffers提供了一种语言无关、平台无关、可扩展的机制,用于以向前兼容和向后兼容的方式序列化结构化数据。 它类似于 JSON,只是它更小更快,并且生成本地语言绑定。
Protocol Buffers是定义语言(以.proto结尾的文件)、proto 编译器生成的与数据接口的代码、特定于语言的运行时库以及写入文件(或通过网络连接发送)的数据的序列化格式的组合。
Protocol Buffers为大小高达几兆字节的类型化结构化数据包提供了一种序列化格式。 该格式适用于临时网络流量和长期数据存储。 可以使用新信息扩展协议缓冲区,而无需使现有数据无效或需要更新代码。
Protocol Buffers是 Google 最常用的数据格式。 它们广泛用于服务器间通信以及磁盘上数据的归档存储。 协议缓冲区消息和服务由工程师编写的 .proto 文件描述。 下面显示了一个示例消息:
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}
proto 编译器在 .proto 文件的构建时被调用,以生成各种编程语言的代码(在本主题后面的跨语言兼容性中介绍)来操作相应的Protocol Buffers。 每个生成的类都包含每个字段的简单访问器和方法,用于序列化和解析整个结构与原始字节之间的关系。 下面向您展示了一个使用这些生成方法的示例:
Person john = Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("[email protected]")
.build();
output = new FileOutputStream(args[0]);
john.writeTo(output);
由于Protocol Buffers在 Google 的各种服务中广泛使用,并且其中的数据可能会保留一段时间,因此保持向后兼容性至关重要。 协议缓冲区允许无缝支持对任何Protocol Buffers的更改,包括添加新字段和删除现有字段,而不会破坏现有服务。 有关此主题的更多信息,请参阅本主题后面的在不更新代码的情况下更新原型定义。
Protocol Buffers非常适合任何需要以语言无关、平台无关、可扩展的方式序列化结构化、类似记录、类型化数据的情况。 它们最常用于定义通信协议(与 gRPC 一起)和数据存储。
使用Protocol Buffers的一些优点包括:
以任何受支持的编程语言编写的代码都可以读取相同的消息。 您可以让一个平台上的 Java 程序从一个软件系统捕获数据,根据 .proto 定义对其进行序列化,然后在另一个平台上运行的单独 Python 应用程序中从序列化数据中提取特定值。
Protocol Buffers编译器 protoc 直接支持以下语言:
Google 支持以下语言,但项目的源代码位于 GitHub 存储库中。 protoc 编译器使用这些语言的插件:
您可以通过在驻留在特定项目代码库之外的 .proto 文件中定义消息类型来跨项目使用Protocol Buffers。 如果您正在定义您预计将在您的直接团队之外广泛使用的消息类型或枚举,您可以将它们放在自己的文件中,而无需依赖。
在 Google 中广泛使用的几个原型定义示例是 timestamp.proto 和 status.proto。
软件产品向后兼容是标准,但向前兼容却不太常见。 只要您在更新 .proto 定义时遵循一些简单的做法,旧代码将毫无问题地读取新消息,而忽略任何新添加的字段。 对于旧代码,删除的字段将具有默认值,删除的重复字段将为空。 有关什么是“重复”字段的信息,请参阅本主题后面的协议缓冲区定义语法。
新代码也将透明地读取旧消息。 旧消息中不会出现新字段; 在这些情况下,Protocol Buffers提供了一个合理的默认值。
协议缓冲区并不适合所有数据。 特别是:
许多外部可用的项目使用协议缓冲区,包括以下内容:
下图显示了如何使用协议缓冲区来处理数据。
Protocol buffers生成的代码提供了实用方法来从文件和流中检索数据、从数据中提取单个值、检查数据是否存在、将数据序列化回文件或流以及其他有用的功能。
以下代码示例向您展示了 Java 中此流程的示例。 如前所示,这是一个 .proto 定义:
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}
编译这个 .proto 文件会创建一个 Builder 类,您可以使用它来创建新实例,如以下 Java 代码所示:
Person john = Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("[email protected]")
.build();
output = new FileOutputStream(args[0]);
john.writeTo(output);
然后,您可以使用 protocol buffers 在其他语言(如 C++)中创建的方法反序列化数据:
Person john;
fstream input(argv[1], ios::in | ios::binary);
john.ParseFromIstream(&input);
int id = john.id();
std::string name = john.name();
std::string email = john.email();
定义 .proto 文件时,您可以指定字段是可选的或重复的(proto2 和 proto3)或单数(proto3)。 (将字段设置为必需的选项在 proto3 中不存在,并且在 proto2 中强烈建议不要使用。有关此内容的更多信息,请参阅指定字段规则中的“必填项”。)
设置字段的可选性/可重复性后,指定数据类型。 协议缓冲区支持通常的原始数据类型,例如整数、布尔值和浮点数。 有关完整列表,请参阅标量值类型。
一个字段也可以是:
message
类型,以便您可以嵌套部分定义,例如用于重复数据集。enum
类型,因此您可以指定一组值以供选择。oneof
类型,当消息有多个可选字段且最多同时设置一个字段时,可以使用该类型。map
类型,用于将键值对添加到您的定义中。在设置可选性和字段类型后,您分配一个字段编号。 字段编号不能改变用途或重复使用。 如果您删除一个字段,您应该保留其字段编号,以防止有人意外重复使用该编号。
Protocol Buffers支持许多标量值类型,包括使用可变长度编码和固定大小的整数。 您还可以通过定义消息来创建自己的复合数据类型,这些消息本身就是可以分配给字段的数据类型。 除了简单和复合值类型之外,还发布了几种常见类型。
Duration
是有符号的、固定长度的时间跨度,例如 42 秒。Timestamp
时间戳是独立于任何时区或日历的时间点,例如 2017-01-15T01:30:15.01Z。Interval
间隔是独立于时区或日历的时间间隔,例如 2017-01-15T01:30:15.01Z - 2017-01-16T02:30:15.01Z。Date
是一个完整的日历日期,例如 2025-09-19。DayOfWeek
是一周中的某一天,例如星期一。TimeOfDay
是一天中的某个时间,例如 10:42:23。LatLng
是一个纬度/经度对,例如 37.386051 纬度和 -122.083855 经度。Money
是具有货币类型的货币数量,例如 42 美元。PostalAddress
是邮政地址,例如 1600 Amphitheatre Parkway Mountain View, CA 94043 USA。Color
颜色是 RGBA 颜色空间中的一种颜色。Month
月份是一年中的一个月,例如四月。Protocol buffers于 2008 年开源,旨在为 Google 以外的开发人员提供与我们在内部从中获得的相同好处的一种方式。 我们通过定期更新语言来支持开源社区,因为我们进行这些更改以支持我们的内部需求。 虽然我们接受来自外部开发人员的精选拉取请求,但我们不能总是优先考虑不符合 Google 特定需求的功能请求和错误修复。