01-Protocol-Buffers简介

Protocol buffers是一种灵活,高效,自动化的机制,用于序列化结构化的数据(如XML),但是它更小,更快,更简单。可以定义数据如何被结构化,然后使用特定的生成的源代码轻松地将结构化数据在各种数据流中写入和读取,这支持各种编程语言。甚至可以更新数据结构,而不会破坏根据“旧”格式编译的已部署程序。

0.1. Protocol buffers如何工作

通过在.proto文件中定义protocol buffers消息类型来指定希望如何构建序列化信息。每个protocol buffers消息都是一个小的逻辑信息记录,包含一系列名称-值对。以下是.proto文件的一个非常基本的示例,该文件定义了包含有关人员信息的消息:

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;
}

如上所示,消息格式很简单:

  • 每种消息类型都有一个或多个唯一编号的字段,
    • 每个字段都有一个名称和一个值类型
    • 其中值类型可以是:
      • 数字(整数或浮点数)
      • 布尔值
      • 字符串
      • 原始字节
      • 甚至(如上例所示)其他protocol buffers消息类型,允许分层次地构建数据

可以指定:

  • 可选字段(optional)
  • 必填字段(required)
  • 重复字段(repeated)

可以在proto3指南中找到有关编写.proto文件的更多信息。

一旦定义了消息,就可以在.proto文件上运行相应编程语言的protocol buffers编译器来生成数据访问类,他们为每个字段提供了简单的访问器,如name()set_name(),以及将整个结构序列化或解析为原始字节的方法。

例如,如果选择的语言是C++,在上面的例子上运行编译器将生成一个名为Person的类。然后,可以在应用程序中使用此类来填充,序列化和检索Personprotocol buffers消息。如下所示的代码。

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

可以在以下位置阅读消息:

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

可以在不破坏向后兼容性的情况下为邮件格式添加新字段,旧的二进制文件在解析时只是忽略新字段。因此,如果通信协议使用protocol buffers作为数据格式,则可以扩展协议,而无需担心破坏现有代码。

将在API参考部分找到有关使用生成的protocol buffers代码的完整参考,在protocol buffers编码中找到有关protocol buffers消息如何编码的更多信息。

0.2. 为什么不直接用XML

对于序列化结构化数据,protocol buffersXML具有许多优点:

  1. 更简单
  2. 缩小3~10倍
  3. 快20~100倍
  4. 更少歧义
  5. 生成更易于编程的数据访问类型

例如,假设要为具有nameemailPerson建模。在XML中,需要:

  <person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
  </person>

而相应的protocol buffers消息(protocol buffers文本格式)是:

# protocol buffer的文本表示
# 这不是在实际传输中的二进制格式
person {
  name: "John Doe"
  email: "jdoe@example.com"
}

当此消息被编码为protocol buffers二进制格式(上面的文本格式只是方便人类可读的表示形式,用于调试和编辑)时,它可能是28字节长并且需要大约100-200纳秒来解析。如果删除空格,XML版本至少为69个字节,并且需要大约5000-10000纳秒才能解析。

此外,操作protocol buffers要容易得多:

cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

而使用XML,必须执行以下操作:

  cout << "Name: "
       << person.getElementsByTagName("name")->item(0)->innerText()
       << endl;
  cout << "E-mail: "
       << person.getElementsByTagName("email")->item(0)->innerText()
       << endl;

但是,protocol buffers并不总是比XML更好的解决方案。例如:

  1. protocol buffers不是使用标记对基于文本的文档(例如HTML)建模的好方法,因为无法轻松地将结构与文本交错
  2. XML是人类可读和可编辑的; protocol buffers在它原生的格式中不是人类可读和可编辑的。
  3. XML在某种程度上也是自我描述的。只有拥有消息定义(如.proto文件)时,protocol buffers才有意义。

0.3. 如何开始使用

下载这个包,它包含JavaPythonC++版本的protocol buffers编译器的完整源代码,以及I/O和测试所需的类。要构建和安装编译器,请按照自述文件中的说明进行操作。

完成所有设置后,请尝试按照所选语言的教程进行操作,这将指导你创建一个使用protocol buffers的简单应用程序。

0.4. proto3简介

最新的版本3发布了一个新的语言版本:Protocol Buffers语言版本3(简称proto3),以及现有语言版本(简称proto2)中的一些新功能。Proto3简化了protocol buffers语言,既易于使用,又可以在更广泛的编程语言中使用:当前的版本允许使用JavaC++PythonJava LiteRubyJavaScriptObjectiveC#来生成protocol buffers代码。此外,可以使用最新的Go protoc插件为Go生成proto3代码,该插件可从golang/protobuf Github存储库获得。更多的语言正在筹备中。

请注意,两种语言版本的API不完全兼容。为避免给现有用户带来不便,将继续在新protocol buffers版本中支持以前的语言版本。

可以在发行说明中看到与当前默认版本的主要差异,并了解Proto3语言指南中的proto3语法。proto3的完整文档即将推出!

(如果名称proto2proto3看起来有点令人困惑,那是因为最初开源protocol buffers时,它实际上是Google的第二版语言,也称为proto2,这也是开源版本号从v2开始的原因。

0.5. 一点小历史

protocol buffers最初是在Google开发的,用于处理索引服务器请求/响应协议。在protocol buffers之前,有一种请求和响应的格式,它使用请求和响应的手动编组/解组,并支持许多版本的协议。 这导致一些非常丑陋的代码,如:

if (version == 3) {
   ...
 } else if (version > 4) {
   if (version == 5) {
     ...
   }
   ...
 }

明确格式化的协议也使新协议版本的推出变得复杂,因为开发人员必须确保请求的发起者和处理请求的实际服务器之间的所有服务器都能理解新协议,然后才能切换以开始使用新协议。

protocol buffers开发用于解决这些问题:

  1. 可以轻松引入新字段,而中间服务器不需要检查数据就可以简单地解析它并传递数据而无需了解所有字段。
  2. 格式更具自我描述性,可以用各种语言(C++,Java等)处理。

但是,用户仍然需要自己手写解析代码。

随着系统的发展,它获得了许多其他功能和用途:

  1. 自动生成序列化和反序列化代码避免了手动解析的需要。
  2. 除了用于短生命周期的RPC(远程过程调用)请求之外,大家已经开始使用protocol buffers作为一种方便的自描述格式用于持久存储数据(例如,在Bigtable中)。
  3. 首先服务的RPC接口被声明为protocol文件的一部分,然后使用protocol编译器生成stub类,用户可以通过服务接口的实际实现来覆盖这些stub类。

现在protocol buffers是Google的数据通用语言,在撰写本文时,Google代码树中定义了306,747种不同的消息类型,跨348,952个.proto文件。它们既可用于RPC系统,也可用于各种存储系统中的数据持久存储。

上次修改: 14 April 2020