目录
一.初识ProtoBuf
1.序列化和反序列化的概念
序列化: 就是将一个对象转换为字节序的过程;
反序列化 就是将一个字节序转换为一个完整对象的过程;
什么时候需要序列化:
存储数据:当你想把的内存中的对象状态保存到⼀个⽂件中或者存到数据库中时。
⽹络传输:⽹络直接传输数据,但是⽆法直接传输对象,所以要在传输前序列化,传输完成后反 序列化成对象。例如我们之前学习过socket编程中发送与接收数据。
如何实现序列化?
主流的序列化和反序列化工具有:XML、JSON、ProtoBuf;
2.什么是ProtoBuf
Protocol Buffers是Google的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法,它可⽤于(数据)通信协议、数据存储等。
Protocol Buffers类⽐于XML,是⼀种灵活,⾼效,⾃动化机制的结构数据序列化⽅法,但是⽐XML更⼩、更快、更为简单。
你可以定义数据的结构,然后使⽤特殊⽣成的源代码轻松的在各种数据流中使⽤各种语⾔进⾏编写和读取结构数据。你甚⾄可以更新数据结构,⽽不破坏由旧数据结构编译的已部署程序
总结:
- 语言无关、平台无关: 即ProtoBuf支持Java、C++、Python等多种主流语言,支持Window、Linux等多个平台
- 高效: 即比XML更小、更快、更为简单。
- 扩展性好、兼容性好: 你可以更新数据结构,而不影响和破环原有的旧程序;
3.ProtoBuf的工作特点
假设在没有ProtoBuf序列化和反序列化工具之前,我们定义了一个类,然后想要对这个类进行序列化和反序列化工作,那么就需要我们自己在该类中设计和构造一个序列化和反序列化的方法,这样的话对于我们的开发效率会有影响。
当我们引入ProtoBuf工具过后,我们就只需要在 .proto文件中定义我们的类即可,不需要为这个类设计任何方法,再使用ProtoBuf的编译器来编译这个.proto文件,就会得到一个.cpp和.hpp的文件,这两个文件中分别放着我们当初在.proto文件中定义的那个类的C++的实现和声明,并且ProtoBuf的编译器在编译的过程中会为我们在.proto文件中定义的那个类自动生成序列化和反序列化方法以及一些与属性字段相关的方法,我们只需要在我们的工作代码中包含以下ProtoBuf编译出来的.cpp和.hpp文件就可以了,在我们的工作代码中就能直接使用,我们定义的类的序列化和反序列化的方法了;
从而省去了我们程序员自己开发一个类的序列化和反序列化的工作;

使用方法:
1.编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
2. 使⽤ protoc 编译器编译 .proto ⽂件,⽣成⼀系列接口代码,存放在新⽣成头⽂件和源⽂件中。
3. 依赖⽣成的接口。将编译⽣成的头⽂件包含进我们的代码中,使用编译器为我们生成的序列化,反序列化方法以及一些对消息字段进行读写的方法。
因此:
ProtoBuf是需要依赖通过编译⽣成的头⽂件和源⽂件来使用的
二.学习思路
对ProtoBuf的完整学习,将使⽤项⽬推进的⽅式完成学习:即对于ProtoBuf知识内容的展开,会对⼀个项⽬进⾏⼀个版本⼀个版本的升级去讲解ProtoBuf对应的知识点.
在后续的内容中,将会实现⼀个通讯录项⽬。对通讯录⼤家应该都不陌⽣,⼀般,通讯录中包含了⼀批的联系⼈,每个联系⼈⼜会有很多的属性,例如姓名、电话等等。
随着对通讯录项⽬的升级,我们对ProtoBuf的学习与使⽤就越深⼊。
三.快速上手
在快速上⼿中,会编写第⼀版本的通讯录1.0。在通讯录1.0版本中,将实现:
• 对⼀个联系⼈的信息使⽤ProtoBuf进⾏序列化,并将结果打印出来.
• 对序列化后的内容使⽤ProtoBuf进⾏反序列,解析出联系⼈信息并打印出来。
• 联系⼈包含以下信息:姓名、年龄。
通过通讯录1.0,我们便能了解使⽤ProtoBuf初步要掌握的内容,以及体验到ProtoBuf的完整使⽤流程。
我们将联系人信息类先定义在.proto文件中,然后利用protoc编译器编译这个文件,然后我们在自己的工作代码中引用这个编译出来的类就好。联系人信息类在经过protoc编译过后会自动变为含有序列化和反序列化方法的C++类,我们只需要直接调用现成的方法即可。
步骤如下:
1. 创建一个.proto文件:
这里我们创建一个PeopleInfo.proto文件,该文件用来定义联系人信息类;

2. 我们需要在这个PeopleInfo.proto文件中完成一些初始化工作。比如:指定PB语法版本、为当前.proto文件中的数据指定作用域;
① 我们可以利用syntax关键字来指定当前.proto文件锁采用的语法版本

注意这个语法版本声明需要放在当前.proto文件的首行(注释不算一行),其次就是我们一般都是需要指定当前.proto文件所采用的语法版本为"proto3"的版本,如果不指定的话当前.proto文件采用proto2语法进行编译,proto2语法相对于proto3语法在编程语言支持上没有proto3广,同时对于一些语法的支持也不是很好,为此实际开发中我们通常采用proto3语法来编译当前文件。
② 我们也可以为当前文件中定义的类声明一个命名空间,来避免不同 .proto之间的命名冲突的问题;
这一点我们可以利用关键字package来实现:

当然,这个命名空间的定义并不是强制的,但是为了养成良好的编程习惯,我们还是希望能将其定义出来;package 定义的命名空间在经过protoc编译器编译过后会变为C++中的namespace 命名空间,其中包含着在当前.proto文件中定义的各种类;
3. 初始化工作都完成的差不多了,我们就可以开始定义类了,在.proto文件中定义类是利用message 关键字来完成的。

在message 定义的类中,我们只需要定义出该类所包含的属性即可,同时我们需要给这些属性进行编号,并且每个同级属性之间不能重复,这是PB语法要求的;
因此在message 中定义的字段格式如下:
同时编号也不是随便乱设的,编号也是有范围的,编号只能从1 ~ 2^29 -1之间进行取数,其中19000 ~ 19999不可取,因为这些编号已经被PB官方征用了!使用了编译就会出错;
在字段命名上,我们也是要注意一点规范,比如使用全小写字母、多个字母之间使用下划线连接;
字段类型可以分为:标量数据类型和特殊数据类型(eg:枚举、其它消息类型);
接下来,我们可以看一看PB中那些标量类型,与C++中的那些类型相对应:
.proto TYPE NOTES C++ TYPE double null double float null float int32 使用变成编码。负数的编码效率比较低(若字段出现负数的频率比较高,可以考虑使用sint32代替) int32 int64 使用变长编码。负数的编码效率比较低(若字段出现负数的频率比较高,可以考虑使用sint64代替) int64 uint32 使用变长编码 uint32 uint64 使用变长编码 uint64 sint32 使用变长编码。符号整型。负数编码效率高于常规int32 int32 sint64 使用变长编码。符号整型。负数编码效率高于常规int64 int64 fixed32 定长4字节。若值常大于2^28则会比uint32更高效 uint32 fixed64 定长8字节。若常值大于2^28则会比uint64更高效 uint64 sfixed32 定长4字节 int32 sfixed64 定长8字节 int64 bool null bool string 包含UTF-8和ASCII编码的字符串,长度不能超过2^32 string bytes 可以包含任意的字节序列但长度不能超过2^32 string 注意:
1.上表中左边对应的是PB提供的类型,右边表示的是该PB类型在C++语法中对应的C++类型,我们在PB文件中定义类时需要使用PB语法提供的类型来定义类,而不是C++语法提供的类型;
2.上面所说的变长编码就是说,在经过protoc编译过后,原本4字节或8字节的数据可能会被以其更少的字节或更多的字节来进行编码(或序列化);举个例子:就比如我们上述在PB文件中定义的PeopleInfo类中的age字段:
在PB文件中是int32类型,但是在经过proto编译过后在C++中就对应int32类型,也就是4字节的数据,当我们在C++中调用序列化方法来编码该字段时,该序列化方法可能会用低于4字节或高于4字节的数据来进行编码,也就是说序列化方法在序列化字段的时候会判断一下当前字段在PB文件中对应的是什么类型,然后再决定采用变长编码还是定长编码的方式来进行编码;假设现在我们将age在PB文件中的类型换为sfixed32类型,那么在使用protoc编译过后在C++中对应的就是int32类型,那么此时在调用序列化编码该字段的时候就会以4字节的方式来序列化该字段,也就是定长编码的方式来序列化!
<
对于反序列化也是同理,反序列化方法会自动识别字段类型和编码方式,从而准确的完成反序列化操作,从而将我们的数据正确解码为C++中正确的数据类型!



最低0.47元/天 解锁文章
3万+





