- 第10章 Protocol Buffer
- 一、什么是Protobuf?
- 二、ProtoBuf的版本
- 三、protocol buffers的工作流程
- 四、为什么不使用XML?
- 五、如何开始使用protocol buffers?
- 六、proto3介绍
- 七、最后说一点历史
第10章 Protocol Buffer
注:本教程使用Proto3
版本
一、什么是Protobuf?
全称Protocol buffers
,是google
的一种数据交换的格式,它独立于语言,独立于平台。
作用:作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储(序列化与反序列化)等诸多领域。
优点:相比较XML和JSON格式,protobuf
更小、更快、更便捷。你只需要将要被序列化的数据结构定义一次(译注:使用.proto
文件定义),便可以使用特别生成的源代码(译注:使用protobuf
提供的生成工具)轻松的使用不同的数据流完成对这些结构数据的读写操作,即使你使用不同的语言(译注:protobuf
的跨语言支持特性)。你甚至可以更新你的数据结构的定义(译注:就是更新.proto
文件内容)而不会破坏依赖“老”格式编译出来的程序。
二、ProtoBuf的版本
PB具有三个版本:
一、Google官方版本: 谷歌官方开发、比较晦涩,主库名字:Google.ProtoBuf.dll
二、.Net社区版本: .Net社区爱好者开发,写法上比较符合.net上的语法习惯,主库名字:protobuf-net.dll
三、.Net社区版本(二): 据说是由谷歌的.net员工为.net开发,在官方没有出来csharp的时候开发,到发博文时还在维护,主库名字:Google.ProtocolBuffers.dll
至于选用那个版本,跨平台的需求不大的话,可以用版本二、大的话可以选用一或者三。
三、protocol buffers的工作流程
首先,你需要通过在.proto
文件中定义protocol buffer
的message
类型来指定你想要序列化的数据结构,每一个protocol buffer message
是一个逻辑上的信息记录,它包含一系列的键值对。这里展示一个最基本的.ptoto
文件的例子,它定义了一个包含Person
信息的message
:
syntax = "proto3";
package shenjun;
message Person
{
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType
{
HOME = 0;
MOBILE = 1;
WORK = 2;
}
message PhoneNumber
{
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 4;
}
正如你所看见的那样,message
的格式非常简单—每一个message
类型都有一个或多个带有唯一编号的字段,每一个字段有一个字段名和一个字段类型,字段类型可以是数值类型(比如整形或浮点型)、booleans
(布尔类型)、strings
(字符串类型)、raw bytes
、甚至(正如上面的例子)还可以是其他的protocol buffer message
类型,这允许你可以分层次的组织你的数据结构。
运行编译器编译上述的例子将生成一个名为Person
的类,在你的应用程序中你可以使用这个类来填充、序列化和反序列化Person protocol buffer messages
。之后你可能会写下如下类似的代码(译注:序列化):
Person p = new Person();
p.Name = "shenjun";
p.Id = 1;
p.Email = "380921128@qq.com";
p.Phone.Add(new Person.Types.PhoneNumber
{
Number = "12345678901",
Type = Person.Types.PhoneType.Mobile
});
p.Phone.Add(new Person.Types.PhoneNumber
{
Number = "123456",
Type = Person.Types.PhoneType.Home
});
byte[] buff = p.ToByteArray();
之后,你可以将你的message
读回(译注:反序列化):
IMessage IMperson = new Person();
Person person = (Person)IMperson.Descriptor.Parser.ParseFrom(buff);
你可以向你的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>jdoe@example.com</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: "jdoe@example.com"
}
当这个message
被编码成protocol buffer
的二进制格式(上述的文本格式只是为了方便阅读、调试和编辑),它将可能占用28个字节长度并且仅需要100-200纳秒的解析时间。相比,XML版本的则至少需要占用69字节的空间(这是在移除XML中的空格、换行之后),同时,将耗费大约5000-10000纳秒的解析时间。
除此之外,手动操作protocol buffer
更为方便,例如如下C++代码:
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;
事物总有两面性,和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#和C++编译器的完整源码,同时包含了你所需要的I/O和测试类。为了完成编译和安装,请参照README文件。
一旦你完成了编译和安装,那么就可以开始使用protocol buffers
了。
六、proto3介绍
我们最新的版本version 3 alpha release
引进了一个新的语言版本—Protocol Buffers version 3
(称之为proto3
),它在我们现存的语言版本(proto2
)上引进了一些新特性。proto3
简化了protocol buffer language
,这使其可以更便于使用和支持更多的编程语言:我们现在的alpha release
版本可以让你能产生JAVA
、C++
、Python
、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
的协议,这个协议支持一个数字版本号,这导致了一个非常丑陋的代码,如下所示:
if (version == 3) {
...
} else if (version > 4) {
if (version == 5) {
...
}
...
}
很显然的,格式化的协议也导致了复杂的新版本推出问题,因为开发人员必须确保所有服务器请求的发起者和实际的请求处理者之间都要理解新的协议。
Protocol buffers 就是用来解决这些问题的:
1、可以很容易的插入新字段,中间的服务器可以简单的解析它而不需要了解所有字段。
2、格式更具有自描述性,可以被不同的语言处理(比如JAVA
、C++
、Python
等)。
3、自动产生序列化和反序列化代码从而避免了手动解析。
4、除了应用在具有短暂生命周期的RPC
请求中,人们开始使用protocol buffers
作为一种便利的自描述格式来存储数据(比如在Bigtable
中)。
5、服务器的RPC
接口开始被声明为协议文件的一部分,通过protocol
编译器产生stub
类,该类可以被用户根据实际实现的服务器接口进行重写。
?