Protocol Buffer

Protocol Buffers

简介

PB全称Protocol Buffers(也简称Protobuf) 是Google 公司内部的混合语言数据标准,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了C++、Java、Python 三种语言的API。

目前已经正在使用的有超过48,162 种报文格式定义和超过12,183

个 .proto 文件。他们用于RPC 系统和持续数据存储系统。

一个简单的例子

这里有一个非常简单的.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;

}

使用PB的编译器编译生成应用程序对应的语言的数据访问的类,于是我们

可能写出如下的C++代码:

Person person;

person.set_name("John Doe");

person.set_id(1234);

person.set_email("jdoe@https://www.360docs.net/doc/714563999.html,");

fstream output("myfile", ios::out | ios::binary);

person.SerializeToOstream(&output);

然后读取数据的时候可以这样写:

fstream input("myfile", ios::in | ios::binary);

Person person;

person.ParseFromIstream(&input);

cout << "Name: " << https://www.360docs.net/doc/714563999.html,() << endl;

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

语言相关规则

a)定义消息结构,例如上面的例子

message Person {

required string name = 1;

required int32 id = 2; // Which page number do we want?

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;

}

b)每个变量必须制定如下的规则(rule):

required:必须只能有1个.

optional:可以有0个或者1个

repeated:可以有任意个

由于历史原因,repeated修饰的基本的数字的类型编码压缩的效率不够好,但是我们可以加选项[packed=true]来提高效率,如下:

repeated int32 samples = 4 [packed=true];

下面是摘自文档的一段话:

Required Is Forever You should be very careful about marking fields as required. If at some point you wish to stop writing or sending a required field, it will be problematic to change the field to an optional field –old readers will consider messages without this field to be incomplete and may reject or drop them

unintentionally. You should consider writing

application-specific custom validation routines for your buffers instead. Some engineers at Google have come to the conclusion that using required does more harm than good; they prefer to use only optional and repeated.

However, this view is not universal.

c)每个变量必须一个数字标签(TAG),如上述例子,tag是用来在压缩

后的二进制数据格式中识别该变量的,一旦定义好消息结构并且已经在程序中进行使用的时候,不应该再更改变量的tag。

1-15占用一个字节,16-2047占用两个字节

Tag的最小值是1,最大值是229 - 1, 或者536,870,911.

(19000-19999也不可以使用,是预留给PB编译器使用的

d)多个消息类型可以定义在一个.proto文件中,如下:

message SearchRequest {

required string query = 1;

optional int32 page_number = 2;

optional int32 result_per_page = 3;

}

message SearchResponse {

...

}

e)optional规则的变量可以设置默认值

optional int32 result_per_page = 3 [default = 10];

f)枚举变量

g)使用其他类型的消息

message SearchResponse {

repeated Result result = 1;

}

message Result {

required string url = 1;

optional string title = 2;

repeated string snippets = 3;

}

h)导入其他定义

import "myproject/other_protos.proto";

i)嵌套类型

j)添加注释

k)Scalar type

PB扩展性

a)extensions

message Foo {

// ...

extensions 100 to 199;

}

其他用户可以导入该定义,并进行扩展

extend Foo {

optional int32 bar = 126;

}

使用时略有不同

Foo foo;

foo.SetExtension(bar, 15);

b)更新消息结构定义

?不修改已经存在的变量的tag编号

?任何新增加的变量都是用optional和repeated定义

?非required的变量可以移除,只要不在使用它的tag编号

?非required变量可以变为extension,只要确保它的tag编号和数据类型不变

?int32, uint32, int64, uint64, 和bool 都相互兼容

?sint32 和sint64 相互兼容

?string 和bytes 相互兼容,只要bytes类型是有效的UTF-8编码

?如果bytes类型包含的是编码后的消息,那么嵌套的消息与bytes 类型兼容

?fixed32与sfixed32兼容, fixed64 与sfixed64兼容.

?optional与repeated兼容

?修改默认值也是可以的

PB 编码讨论

ProtoBuf编码基础——Varints, varints是一种将一个整数序列化为一个或者多个Bytes的方法,越小的整数,使用的Bytes越少。

Varints的基本规则是:

a)每个Byte的最高位(msb)是标志位,如果该位为1,表示该Byte后面

还有其它Byte,如果该位为0,表示该Byte是最后一个Byte。

b)每个Byte的低7位是用来存数值的位

c)Varints方法用Litte-Endian(小端)字节序

举个例子:300用Varints序列化的结果是1010 1100 0000 0010,运算过程

如下所示:

1010 1100 0000 0010->010 1100 000 0010(去标志位)->

000 0010 010 1100(调整字节序)-> 1 0010 1100 ->256+32+8+4=300(计算值)

ProtoBuf中消息的编码规则:

a)每条消息(message)都是有一系列的key-value对组成的, key和value

分别采用不同的编码方式。

b)对某一条件消息(message)进行编码的时候,是把该消息中所有的

key-value对序列化成二进制字节流;而解码的时候,解码程序读入二

进制的字节流,解析出每一个key-value对,如果解码过程中遇到识别

不出来的类型,直接跳过。这样的机制,保证了即使该消息添加了新的

字段,也不会影响旧的编/解码程序正常工作。

c)key由两部分组成,一部分是在定义消息时对字段的编号(field_num),

另一部分是字段类型(wire_type)。字段类型定义如下表所示。

d)key的编码方式:field_num << 3 | wire_type

e)varint类型(wire_type=0)的编码,与第(1)部分中介绍的方法基本一致,

但是int32, int64和sint32,sint64有些特别之处:int32和int64就是

简单的按varints方法来编码,所以像-1、-2这样负数也会占比较多的

Bytes。于是sint32和sint64采用了一种改进的方法:先采用Zigzag

方法将所有的整数(正数、0和负数)一一映射到所有的无符号数上,

然后再采用varints编码方法进行编码。Zigzag映射函数为:

Zigzag(n) = (n << 1) ^ (n >> 31), n为sint32时

Zigzag(n) = (n << 1) ^ (n >> 63), n为sint64时

这样映射后再进行编码的好处就是绝对值比较小的负数序列化后的结果

占的Bytes数也会比较少。

f)64-bit(wire_type=1)和32-bit(wire_type=5)的编码方式就比较简单了,

直接在key后面跟上64bits或32bits,采用Little-Endian(小端)字节

序。

g)length-delimited(wire_type=2)的编码方式:key+length+content,

key的编码方式是统一的,length采用varints编码方式,content就

是由length指定的长度的Bytes。

h)wire_type=3和4的现在已经不推荐使用了,因此这里也不再做介绍。基于PB的流的讨论

PB序列化之后的二进制数据只是简单的压缩之后的数据内容,没有对消息的头和尾做任何标记,这样我们在网络传输PB数据的时候,如果传输多个消息的话,如何解析就是程序员自己需要解决的事情了,最简单的方法是在往流写入消息之前,先写入该消息的大小,然后再写入消息本身,这样读数据的一段可以

根据消息的大小读到一个完整的消息。

进一步的方案思考:如何做一个通用的基于PB的网络传输框架?

https://www.360docs.net/doc/714563999.html,/Solstice/archive/2011/04/03/2004458.html

基于PB的强大的反射功能。

PB无法传输过大数据

官方文档介绍,PB不是为传输大数据而设计的,如果传输的消息大小大于1M的话,是该考虑其他方案的时候了。

PB提供了一个类,CodedInputStream(CodedOutputStream),用来方便向流写入或读取PB数据,但是这类的内部有限制,读取的总数据不能超过64M,如果超过的话,会拒绝接受进一步的数据。

PB的性能讨论

Protobuf 的优势

官方文档说protocol buffer有很多XML不具备的优点:

?简单;

?小巧:3-10倍

?效率高:20-100倍

?无二义性

?有自动工具生成访问类

你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用

Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。

它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的field 并不会引起已经发布的程序的任何改变。

Protobuf 语义更清晰,无需类似XML 解析器的东西(因为Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对Protobuf 数据进行序列化、反序列化操作)。

使用Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。

Protobuf 的不足

Protbuf 与XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。XML 已经成为多种行业标准的编写工具,Protobuf 只是Google 公司内部使用的工具,在通用性上还差很多。由于文本并不适合用来描述数据结构,所以Protobuf 也不适合用来对基于文本的标记文档(如HTML)建模。另外,由于XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出Protobuf 的任何内容

相关文档
最新文档