【32450新蒲京网站】Protocol Buffers的行使

2019-11-26 15:02 来源:未知
  1. Protocol Buffers的介绍

1  Protocol Buffers的介绍

Protocol Buffers是一种用于序列化结构化数据的机制,它具有灵活、高效、自动化的特点。类似于XML,但是比XML更小巧、快捷、简单。在Google 几乎所有它内部的RPC协议和文件格式都是采用PB。
PB具有以下特点:

  1. 平台无关、语言无关
  2. 高性能 比XML块20-100倍
  3. 体积小 比XML小3-10倍
  4. 使用简单
  5. 兼容性好

在这里,我做了个小实验,将一个29230KB的自定义格式的文本数据转换成PB和XML:

 

PB

XML

转换后的大小

21011KB

43202KB

解析时间(100次循环)

18610ms

169251ms

完成解析所写代码行数

1行

50行

与官方说法的差距,主要可能是因为应用场景不同,我的测试数据中字段比较长

32450新蒲京网站,表1:PB与XML的实验比较

可见,PB作为一种轻量级的数据协议,在时间、空间上都有一定的优势。

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages – Java, C++, or Python. You can even update your data structure without breaking deployed programs that are compiled against the “old” format.(摘自Protocol Buffers官网)

2  Protocol Buffers的简单应用

protocol buffers是google提供的一种将结构化数据进行序列化和反序列化的方法,其优点是语言中立,平台中立,可扩展性好,目前在google内部大量用于数据存储,通讯协议等方面。Protocol Buffers在功能上类似XML,但是序列化后的数据更小,解析更快,使用上更简单。用户只要按照proto语法在.proto文件中定义好数据的结构,就可以使用Protocol Buffers提供的工具(protoc)自动生成处理数据的代码,使用这些代码就能在程序中方便的通过各种数据流读写数据。PB目前支持Java, C++和Python3种语言。另外,Protocol Buffers还提供了很好的向后兼容,即旧版本的程序可以正常处理新版本的数据,新版本的程序也能正常处理旧版本的数据。

2.1  创建流程

Protocol Buffers具有以下特点:

2.1.1  定义一个.proto文件

新建一个文件,命名为addressbook.proto,内容如下:

package tutorial;``//命名空间

 

option java_package = ``"com.example.tutorial"``;``//生成文件的包名

option java_outer_classname = ``"AddressBookProtos"``;``//类名

 

message Person { ``//要描述的结构化数据

 

``required string name = ``1``;``//required表示这个字段不能为空

``required int32 id = ``2``;``//等号后面的内容为数字别名

``optional string email = ``3``;``//optional表示可以为空

 

``PhoneNumber {``//内部message

``required string number = ``1``;

``optional int32 type = ``2``;

``}

 

``repeated PhoneNumber phone = ``4

}

 

message AddressBook {

``repeated Person person = ``1``;``//是个集合

}

对以上内容的一点解释:

  • PB所支持的元类型数据请参考:PB元类型数据
  • 修饰符required:这个修饰符应该谨慎使用,滥用会导致后续的修改容易出现兼容性问题;
  • 修饰符optional:对于常出现的属性,为节省空间应该取1-16的别名;
  • PB是以key-value的形式来将结构化数据序列化的。它采用了将等号后的数字别名以及属性的类型用varints编码成一个数字,来作为key。
  1. 平台无关、语言无关
  2. 高性能 比XML块20-100倍
  3. 体积小 比XML小3-10倍
  4. 使用简单
  5. 兼容性好

2.1.2  使用PB编译器

输入:protoc      -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
其中    -I指定.proto文件所在目录
–java_out指定生成java文件所在的目录

2、message的编码特点

2.1.3  使用PB的API来写入和读取messages

经过以上步骤,会在指定的$DST_DIR目录下生成一个AddressBookProtos.java的类。在maven中引入protobuf-java这个依赖后,利用这个类,便能序列化/反序列化数据了。
生成的代码结构如下:

class AddressBookProtos{

``class Person{

``class PhoneNumber{``class Builder{} }

``class Builder{}

``}

``class AddressBook{``class Builder{} }

} 

可以看到Person、PhoneNumber、AddressBook这些内部类则对应了所定义的那些message。

Protocol Buffers 之所以解析速度快、所占体积小,很大程度上是由它序列化的编码特点来决定的。

2.2  序列化数据及分析

通过阅读代码可以看到,以上三个类的成员变量都是private类型的,并且,只提供了getter方法,而没有提供setter方法去为数据变量赋值。
PB利用了内部类可以访问到外部类中私有成员变量的特性。对外部类的任何赋值操作都需要通过Builder内部类来进行。Builder中有一个指向外部类的引用(名为result),当赋值完成,调用Builder的build()方法时,会把这个对象返回,同时使result指向null。
PB通过这样一种方式保证了数据安全性,一旦数据构建完毕,将无法再对其进行修改。
拿PhoneNumber这个类来说,对成员变量number、type赋值,需要以如下方式来进行:

PhoneNumber.Builder builder = PhoneNumber.newBuilder();

 

//调用setter赋值,setter返回了this,所以可以链式表述

builder.setNumber(``"111"``).setType(``1``);

 

//赋值完成后,调用Builder的build方法,将返回PhoneNumber对象

PhoneNumber phoneNumber = builder.build();

构建完成后,可以调用writeTo方法,将数据写入数据流中。

2.1 Base 128 Varints

2.3  反序列化及分析

一行代码便能完成反序列化:

AddressBook  list = AddressBook .parseFrom(inputStream或buffer);

背后PB做了很多事情:

  1. 根据inputStream或者buffer去构造一个CodedInputStream;
  2. 然后使用生成代码中的mergeFrom方法,去解析二进制数据:
    首先调用CodedInputStream的readTag,也就是从中取得key值(int类型),然后通过swtich块来往对象中赋值(PB采用了Base 128 Varints的方式来编码这个数字,后面会介绍这种方式的)。
  3. 将数据解析完成后,会调用build()方法,将构建好的对象返回。 

 

更多详情见请继续阅读下一页的精彩内容: http://www.linuxidc.com/Linux/2014-09/107283p2.htm

32450新蒲京网站 1

Protocol Buffers采用了Base 128 Varints来变长编码整数:

  1. 变长编码的整数,它可能包含多个byte,对于每个byte的8位,其中后7位表示数值,最高的一位表示是否还有还有另一个byte,0表示没有,1表示有;
  2. 越前面的byte表示数值的低位,越后面的byte表示数值的高位;

例子:
300 varints 编码为:1010 1100 0000 0010
解释如下:
300的2进制编码为:0001 0010 1100
按照刚才的规则,高低位颠倒,截取最后的7为放在第一个byte,则第一byte为1010 1100(其中最高位1表示,后续还有byte);接着剩下的内容放到第二个byte,为0000 0010(其中最高位0表示,后续无byte,这个数到这里截止了)。
于是,合在一起为 1010 1100 0000 0010;

2.2 Key-Value

如前所述,Protocol Buffers的message是一系列的key-value对,在二进制数据中,使用varints数字(包含了别名以及属性类型信息)来作为key,进而通过由PB编译器生成的代码来构造以及解析数据。
Protocol Buffers将 key编码成下面的结构:
X YYYY ZZZ
其中:最高位X表示是否还有后续的byte来编码数字别名;YYYY用于编码别名,定义了多余16个属性,则需要用到额外的byte,所以出现频率高的字段应当取1-16的别名);ZZZ表示这个字段的类型,PB支持的属性的对应规则如下表:

Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32,sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages,packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, floa

表2:PB 属性对应规则
例子:
required int32 a=1; 在应用中给a赋值150 ,序列化后08 96 01

  • 08代表的是key 0 0001 000, 最高位为0,表示这个key为一个byte,中间四位表示a的数字别名,最后三位表示a的属性类型;
  • 96 01代表的是value,二进制为:1001 0110 0000 0001
    → 001 0110 000 0001(去掉最高位)
    → 22 + 1*2^7 = 150

2.3 Zig-Zag

采用varints的方式编码有符号的整数,效率比较差,因为负数的最高位是1,这样就导致了情况类似于编码一个很大的数。

为了解决这个问题,Protocol Buffers定义了sint32/sint64属性,他们采用了“之字形”(ZigZag)编码的方式,将负数编码成正数,交替进行。看了下表就很好理解了:

Signed Original Encoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
2147483648 4294967295

表3:Zig-Zag编码规则

利用这个方式,可以有效地节省存储空间,也能提高解析效率。了解了以上内容,对于其他数据类型的编码,也是很好理解的,大家可以参考官方文档,这里不做详述。

3.为什么不用XML?

ProtocolBuffer拥有多项比XML更高级的串行化结构数据的特性,ProtocolBuffer:

· 更简单

· 小3-10倍

· 快20-100倍

· 更少的歧义

· 可以方便的生成数据存取类

例如,让我们看看如何在XML中建模Person的name和email字段:

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

对应的ProtocolBuffer报文则如下:

#ProtocolBuffer的文本表示
#这不是正常时使用的二进制数据
person {
name: "John Doe"
email: "jdoe@example.com"
}

当这个报文编码到ProtocolBuffer的二进制格式( )时(上面的文本仅用于调试和编辑),它只需要28字节和100-200ns的解析时间。而XML的版本需要69字节(除去空白)和5000-10000ns的解析时间。

当然,操作ProtocolBuffer也很简单:

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()
<< end;

当然,ProtocolBuffer并不是在任何时候都比XML更合适,例如ProtocolBuffer无法对一个基于标记文本的文档建模,因为你根本没法方便的在文本中插入结构。另外,XML是便于人类阅读和编辑的,而ProtocolBuffer则不是。还有XML是自解释的,而 ProtocolBuffer仅在你拥有报文格式定义的 .proto 文件时才有意义。

 

相关文章:

.net自带二进制序列化,XML序列化和ProtoBuf序列化的压缩对比

WCF服务上应用protobuf

玩转Protocol Buffers

Beetle使用Protobuf.net进行对象序列化传输

Google Protocol Buffer 的使用和原理

Protocol Buffers and WCF

Protobuf-net: the unofficial manual

Working with Protobuf WCF Services

 

TAG标签:
版权声明:本文由32450新蒲京网站发布于葡萄游戏厅_棋牌游戏,转载请注明出处:【32450新蒲京网站】Protocol Buffers的行使