声明:本文大部分内容翻译自官方英文文档,其中可能穿插着加入自己的语言用以辅助理解,本文禁止转载。
一、什么是protocol buffers
Protocol buffers是一个灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,与XML相比,Protocol buffers序列化后的码流更小、速度更快、操作更简单。你只需要将要被序列化的数据结构定义一次(译注:使用.proto文件定义),便可以使用特别生成的源代码(译注:使用protobuf提供的生成工具)轻松的使用不同的数据流完成对这些结构数据的读写操作,即使你使用不同的语言(译注:protobuf的跨语言支持特性)。你甚至可以更新你的数据结构的定义(译注:就是更新.proto文件内容)而不会破坏依赖“老”格式编译出来的程序。
二、protocol buffers的工作流程
首先,你需要通过在.proto文件中定义protocol buffer的message类型来指定你想要序列化的数据结构,每一个protocol buffer message是一个逻辑上的信息记录,它包含一系列的键值对。这里展示一个最基本的.ptoto文件的例子,它定义了一个包含Person信息的message:
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的格式非常简单--每一个message类型都有一个或多个带有唯一编号的字段,每一个字段有一个字段名和一个字段类型,字段类型可以是数值类型(比如整形或浮点型)、booleans(布尔类型)、strings(字符串类型)、raw bytes、甚至(正如上面的例子)还可以是其他的protocol buffer message类型,这允许你可以分层次的组织你的数据结构。你可以单独指定每一个字段为optional fields(可选字段)、required fields(必须字段)、repeated fields(可重复字段)。下一篇博文将会对.proto文件进行更详细的描述。
一旦定义了你的message,你就可以根据你所使用的语言(译注:如JAVA、C++、Python等)使用protocol buffer提供的编译工具编译.proto文件生成数据访问类。这些类为每一个字段都提供了简单的访问器(比如name()和set_name()),同时还提供了将整个结构化数据序列化为原始字节数据以及从原始字节数据反序列化为结构化数据的方法(译注:C++中称之为函数)。例如,如果你使用的语言是C++,运行编译器编译上述的例子将生成一个名为Person的类,在你的应用程序中你可以使用这个类来填充、序列化和反序列化Person protocol buffer messages。之后你可能会写下如下类似的代码(译注:序列化):
1 Person person; 2 person.set_name("John Doe"); 3 person.set_id(1234); 4 person.set_email("[email protected]"); 5 fstream output("myfile", ios::out | ios::binary); 6 person.SerializeToOstream(&output);
之后,你可以将你的message读回(译注:反序列化):
1 fstream input("myfile", ios::in | ios::binary); 2 Person person; 3 person.ParseFromIstream(&input); 4 cout << "Name: " << person.name() << endl; 5 cout << "E-mail: " << person.email() << endl;
你可以向你的message中添加新的字段而不会破坏前向兼容性;在解析时旧的二进制文件会简单的忽略掉新字段,所以,如果你的通信协议中使用protocol buffers作为数据交换格式,那么你可以扩展你的协议而不用担心会打乱现有的代码。
三、为什么不使用XML?
相对于XML,protocol buffers在序列化结构数据时拥有许多先进的特性:
1、更简单
2、序列化后字节占用空间比XML少3-10倍
3、序列化的时间效率比XML快20-100倍
4、具有更少的歧义性
5、自动生成数据访问类方便应用程序的使用
举个例子,如果你想描述一个具有name和email的person数据结构,在XML中,你需要这样做:
<person> <name>John Doe</name> <email>[email protected]</email> </person>
然而,在protocol buffers的message中(protocol buffers的文本格式)你需要这样做:
# Textual representation of a protocol buffer. # This is *not* the binary format used on the wire. person { name: "John Doe" email: "[email protected]" }
当这个message被编码成protocol buffer的二进制格式(上述的文本格式只是为了方便阅读、调试和编辑),它将可能占用28个字节长度并且仅需要100-200纳秒的解析时间。相比,XML版本的则至少需要占用69字节的空间(这是在移除XML中的空格、换行之后),同时,将耗费大约5000-10000纳秒的解析时间。
除此之外,手动操作protocol buffer更为方便,例如如下C++代码:
1 cout << "Name: " << person.name() << endl; 2 cout << "E-mail: " << person.email() << endl;
然而如果你使用XML,那么你将需要这样做:
1 cout << "Name: "<< person.getElementsByTagName("name")->item(0)->innerText()<< endl; 2 cout << "E-mail: "<< person.getElementsByTagName("email")->item(0)->innerText()<< endl;
事物总有两面性,和XML相比protocol buffers并不总是更好的选择,例如,protocol buffers并不适合用来描述一个基于文本的标记型文档(比如HTML),因为你无法轻易的交错文本的结构。另外,XML具有很好的可读性和可编辑性;而protocol buffers,至少在它们的原生形式上并不具备这个特点。XML同时也是可扩展、自描述的。而一个protocol buffer只有在具有message 定义(在.proto文件中定义)时才会有意义。
四、如何开始使用protocol buffers?
首先,可以在这里下载安装包或者源码包
https://developers.google.com/protocol-buffers/docs/downloads#release-packages
这包含了针对JAVA、Python和C++编译器的完整源码,同时包含了你所需要的I/O和测试类。为了完成编译和安装,请参照README文件。
一旦你完成了编译和安装,那么就可以开始使用protocol buffers了,后续的博文将会对C++和JAVA语言的具体使用细节进行阐述。
五、proto3介绍
我们最新的版本version 3 alpha release引进了一个新的语言版本--Protocol Buffers version 3 (称之为proto3),它在我们现存的语言版本(proto2)上引进了一些新特性。proto3简化了protocol buffer language,这使其可以更便于使用和支持更多的编程语言:我们现在的alpha release版本可以让你能产生JAVA、C++、Pthyon、JavaNano、Ruby、Objective-C和C#版本的protocol buffer code,不过可能有时会有一些局限性。另外,你可以使用最新的Go protoc插件来产生Go语言版本的proto3 code,这可以从golang/protobuf Github repository获取。
我们现在只推荐你使用proto3:
1、如果你想尝试在我们新支持的语言中使用protocol buffers
2、如果你想尝试我们最新开源的RPC实现gRPC(目前仍处于alpha release版本),我们建议你为所有的gRPC 服务器和客户端都使用proto3以避免兼容性问题。
注意两个版本的语言APIs并不是完全兼容的,为了避免给原来的用户造成不便,我们将会继续维护之前的那个版本(译注:proto2)。
六、最后说一点历史
Protocol buffers最初被Google开发用来作为处理索引服务器的request/response协议。在protocol buffers诞生之前,有一个需要手动编码/解码requests、responses的协议,这个协议支持一个数字版本号,这导致了一个非常丑陋的代码,如下所示:
1 if (version == 3) { 2 ... 3 } else if (version > 4) { 4 if (version == 5) { 5 ... 6 } 7 ... 8 }
很显然的,格式化的协议也导致了复杂的新版本推出问题,因为开发人员必须确保所有服务器请求的发起者和实际的请求处理者之间都要理解新的协议。
Protocol buffers就是用来解决这些问题的:
1、可以很容易的插入新字段,中间的服务器可以简单的解析它而不需要了解所有字段。
2、格式更具有自描述性,可以被不同的语言处理(比如JAVA、C++、Python等)。
3、自动产生序列化和反序列化代码从而避免了手动解析。
4、除了应用在具有短暂生命周期的RPC请求中,人们开始使用protocol buffers 作为一种便利的自描述格式来存储数据(比如在Bigtable中)。
5、服务器的RPC接口开始被声明为协议文件的一部分,通过protocol 编译器产生stub类,该类可以被用户根据实际实现的服务器接口进行重写。