目录
前言
在网络的学习中,我们知道了如果想要进行跨网络通信就必须要定义“协议”,而“协议”可以帮助网络通信的两台主机解析数据,进而实现通信的效果。网路通信的方式有很多,但是都离不开一件事,那就是对数据进行序列化和反序列化。而我们常常听到的Json就是序列化工具的一种,本文要介绍的是另一种由Google开发的一种序列化工具。
一、protobuf的简介
当我们自己手写序列化和反序列化程序的时候,我们常常要做一些比较耗时但没有什么技术含量的工作,比如对一个结构体中的数据赋值、清空等函数的编写。protobuf工具为我们省去了这一过程,我们只需要编写一个后缀名位proto文件指定各个消息的关系即可(protobuf以消息为序列化单位),,而后protobuf会为我们生成两个后缀名分别为pb.cc和pb.h的文件,这两个文件中就包含了一些常见的操作,比如说对数据进行赋值或清空等。而后我们就可以使用这些接口来编写我们的主要代码逻辑了。
![](https://i-blog.csdnimg.cn/direct/5f5c554a15cd4484a9d61964366b8042.png)
二、编写一个.proto文件
1.选择使用的protobuf版本
在创建好一个后缀名为.proto的文件后我们要注意的第一个点就是,要在除去注释的第一行指明使用的protobuf版本,如果不对版本进行指明,那么编译器将自动使用“proto2”。
syntax="proto3"
2.指定命名空间(package)
在实现一个项目的时候,我们要对每个“消息”的作用域进行一个规定,也是为了我们代码中减少冲突而必要的一个操作。我们通常使用package声明符进行声明。
3.定义一个“消息”(message)
无论是在网络传输还是在数据持久化存储中,我们都会将数据进行结构化。结构化的数据一方面是为了描述一件复杂的事物(比如说一个“人”往往有多个属性,单一属性是难以描述的)所必需的,另一方面,结构化的数据更方便管理和传输。(在传输和存储的时候,如果数据不以结构化来表示,而是以零散的内置类型来表示,那么当数据丢失时,是较难发现的)。
message 消息名称
{
//数据....
}
4.定义消息字段
标量类型:
![](https://i-blog.csdnimg.cn/direct/906510defce845ff82775e9205b5300c.png)
注:变成编码指的是:protobuf编码后,该数据类型修饰的数据可能大小变为其他字节数。(当数据用较少字节就能表示的时候,就变小,如果当前数据使用当前字节数不能保存,那么就有可能变大)。
这里还要额外说明几个类型:
这几个类型只做文字说明,后续代码中有体现,这里就不进行代码示例了
枚举类型(enum):
就像C语言中的枚举类型一样,可以用大写字母代之常量,在protobuf中需要注意的是:
①零值常量必须存在且应为枚举中的第一个元素,这是为了兼容proto2的语义
②枚举常量可以嵌套定义
③枚举常量的值应该在32为整数范围内,同时由于①和编码限制负数无效
多选一类型(oneof):
oneof结构中的数据只有一个会被选中,如果多次对该结构变量进行数据输入,以最后一次输入为准。
①该结构中的变量编号不能与已有编号重复
②不能在该结构中使用reapted字段
③多次输入取最后一次输入作为有效输入,并将结构中的其他数据清空
映射类型(map):
map<key,value>
①key是除float和bytes以外的任意标量类型,value可以是任意类型
②map字段不可以用reapted字段修饰
③map中的数据是无序的,但key不可以重复
泛型类型(Any):
可以在/usr/local/protobuf/include/google/protobuf/该路径下查看所有可定义的类型。
注意:为了避免影响观感该类型将于reapted一同在最后单独演示
数组(reapted):
注意:为了避免影响观感该类型将于reapted一同在最后单独演示
5.写一个简单的.proto文件
syntax="proto3";
package Person;
//性别枚举
enum Gender
{
MAN=0;
WOMAN=1;
SECRECY=2;
}
//定义一个“人信息”消息
message PersonInfo
{
int32 age=1;
string ID=3;
Gender sex=4;
double height=5;
//oneof类型
oneof contact_information
{
string qq=8;
string wechat=9;
string email=10;
}
//map类型
map<string,string> ramark=2;
}
我们编写完该文件之后,我们可以在终端中输入该命令来使用该规则生成.pb.cc和.pb.h文件
protoc --cpp_out=. person.proto
![](https://i-blog.csdnimg.cn/direct/a71b0b1404044420aba9e7674d9759a0.png)
![](https://i-blog.csdnimg.cn/direct/06ddf01b9aae4ddab103dad6e6d78bb3.png)
6.看看生成的文件中为我们生成了哪些方法
我们点解生成文件中的.h文件
![](https://i-blog.csdnimg.cn/direct/31dd53a7479c47778616cd699a485315.png)
如果你不喜欢手动滑翻查找所需要的信息,可以使用组合键ctrl+f来打开搜索框,将对应的搜索内容输入到搜索框中查找即可,这里要注意的是如果你要使用组合键查找某一字段的方法最好将搜索框中的内容写为:
[定义的变量][空格]=[空格][变量对应的编号]
![](https://i-blog.csdnimg.cn/direct/e075c6f9e83c4f898f2291a6493565f3.png)
三、使用protpbuf生成的方法
请注意:本阶段只对语法进行解析,故不作错误处理。最后在编写一个项目的时候会对错误处理进行添加,在项目中不对可能的错误处理是一个很不好的选择。
#include <iostream>
#include <string>
#include <fstream>
#include "person.pb.h"
using namespace std;
void printperson(Person::PersonInfo& tmp)
{
cout<<"age: "<<tmp.age()<<endl;
cout<<"ID: "<<tmp.id()<<endl;
cout<<"sex: "<<tmp.sex()<<endl;
cout<<"height: "<<tmp.height()<<endl;
cout<<"qq: "<<tmp.qq()<<endl;//这里应该做特殊判断,但是我们已经知道到底传入了那个联系方式,这里就不做判断了
for(const auto &e:tmp.ramark())
{
cout<<"key: "<<e.first<<"value: "<<e.second<<endl;
}
}
int main()
{
//向消息中写入数据
Person::PersonInfo personin;
personin.set_age(21);
personin.set_id("10086");
personin.set_sex(Person::Gender::SECRECY);
personin.set_height(170.8);
personin.set_qq("10089");
personin.mutable_ramark()->insert({"hello","world"});
//向二进制文件中写入数据
fstream write("person.bin",ios::out|ios::trunc|ios::binary);
personin.SerializeToOstream(&write);
write.close();
//从二进制文件中读取数据
fstream read("person.bin",ios::in|ios::binary);
Person::PersonInfo personout;
personout.ParseFromIstream(&read);
printperson(personout);
read.close();
return 0;
}
![](https://i-blog.csdnimg.cn/direct/c391cdad5e0c4beea32157df296da412.png)
解释:protobuf生成的给变量赋值的方法通常是set_xxx,但是在对map赋值的时候我们使用的是mutable_xxx这个接口的意思是为我们开辟这样一份map空间供我们使用。
总结
本文讲解了一些与protobuf有关的简单操作,下一篇文章将对其他的一些字段与protobuf在网络中如何使用进行介绍。