一、 protobuf是什么
protobuf,Protocol Buffer (简称Protobuf) 是Google开源的性能优异、跨语言、跨平台的序列化库。
序列化(serialization、marshalling)的过程是指将数据结构或者对象的状态转换成可以存储(比如文件、内存)或者传输的格式(比如网络)。反向操作就是反序列化(deserialization、unmarshalling)的过程。
protobuf是一个很好的序列化工具, 因为其二进制序列化,确保了数据传输的保密性和占用带宽小的特性,并且数据结构前后兼容。用来保存数据和传输数据都是非常不错的选择。
目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,所以很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。
Protobuf支持很多语言,比如C++、C#、Dart、Go、Java、Python、Rust等,同时也是跨平台的,所以得到了广泛的应用。
二、protobuf在windows10生成库
环境:系统:Win10 x64
编译器:Qt5.12.0 forWindows MinGW 64位版 和 cmake
protobuf版本:protobuf-cpp-3.8.0.zip
- protobuf开源地址:https://github.com/protocolbuffers/protobuf
- 找到想要的版本,下载protobuf源码:https://github.com/protocolbuffers/protobuf/tags
- 下载cmake安装:https://cmake.org/download/ 。我装的是 cmake-3.17.0-win64-x64.msi 。 安装成功后把cmake的安装路径添加到系统的环境变量中;
- 安装Qt,记得安装时选择MinGW 64位的编译器。安装成功后把Qt的安装路径添加到系统的环境变量中,如下:
这时Cmake通过环境变量来找到Qt使用的编译器来打包protobuf库,在Qt中编译protobuf的编译器 和 我们自己编辑的代码使用同一个编译器来编译代码,可以避免因为编译器类型不同(VS和Qt使用不同的编译器)、同类型的编译器版本不同(32位版本和64位版本)等方面的问题。 - 使用cmake编译protobuf的.a库、根据proto文件编译生成对应的类文件的编译工具protoc.exe。
打开cmake,点击Browse Source选取protobuf源码的路径,然后新建一个build文件夹,用来存放生成的库,并且通过Browse Build来添加到Where to build the binaries一项中。
接着点击Configure,在下拉选择框中选取MinGW Makefiles。
继续点击Configure后,可能会出现下面情况。通过查看错误信息发现是因为缺少某个模块而无法生成测试tests,这里可以将BUILD_TESTS一项取消来跳过生成tests。
重新点击Configure,会看到下方出现Configuring done,再点击Generate,会出现Generating done。而在build文件夹中会出现下面文件。这样就生成了makefile文件;
在cmd中进入build文件夹,使用mingw32来进行编译。编译成功后会在build目录下生成libprotobuf.a、libprotobuf-lite.a、libprotoc.a、protoc.exe这四个文件。
至此已经成功编译完成protobuf的.a库,和根据proto文件编译生成对应的类文件的所需要的编译工具protoc.exe 。
三、定义proto文件
本次应用porobuf的目的是,android设备使用protobuf序列化项目算法所需要的原始数据,然后在Qt上使用C++语言反序列化android设备采集到的原始数据,得到的数据用于算法调优。我们定义的proto文件如下:
syntax = "proto3";
package cn.proto.java.model; //输出的包名
option java_outer_classname = "MsgObs"; //输出的类名
message ObsData {
int32 n = 1;
int32 nmax = 2;
message GpsTime {
int64 time = 1;
double sec = 2 ;
}
message Data {
GpsTime gtime = 1;
int32 sat = 2;
int32 rcv = 3;
}
repeated Data data = 3;
repeated bytes corsObs = 4;
}
至于proto语法教程,参考其他资料。
由于我们需要序列化的数据,是多个message一起序列化,所以使用了一个message的长度(用4个字节表示)+ message序列化数据的格式保存到文件中。
四、使用protoc.exe工具生成proto文件对应的操作类
由于在使用protobuf时要先构建一个proto文件,然后生成对应的头文件和源文件,才能够使用。
在cmd打开的doc命令窗口,把当前目录切换到第二大步所生成的protoc.exe所在的目录,然后键入如下命令:protoc --cpp_out=. struct_obs.proto回车,生成对应的struct_obs.pb.h和struct_obs.pb.cc类文件。在生成的类中,每个proto文件的字段都有相对应的类成员变量对应,还有对应set/get类成员函数来操作这些成员变量。后面就可以创建这个类对象,来操作(读写)类成员变量了。
三、在windows Qt上使用protobuf库进行反序列化
创建Qt项目,首先在Qt的项目pro文件加入protobuf库(注意-lpthread要放在后面),还要把protobuf源码下的src文件夹拷到Qt项目下:
这样protobuf使用环境就配置好了,就可以开始写代码反序列化了:
void SolutionWithProtobufFile(string filenane, int ctx_id, FILE *fp) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
ObsData data;
fstream input(filenane, ios::in | ios::binary); // "../cui/message-2020_03_20_17_39_46.bin"
if(!input.is_open()) {
cout << "open bin file fail." << endl;
return ;
}
input.seekg(0,ios::end);
streampos sp=input.tellg(); //sp为定位指针,因为它在文件结束处,所以也就是文件的大小
//cout<<"file size:"<<endl<<sp<<endl;
input.seekg(0, std::ios::beg); // go back to the beginning
char *buffer = new char[sp]; // allocate memory for a buffer of appropriate dimension
input.read(buffer, sp); // read the whole file into the buffer
//int readedBytes = input.gcount(); //看刚才读了多少字节
//cout << "readedBytes " << readedBytes << endl;
int pos = 0; //表示长度+message开始的位置
obs_t_f gnssobs; //设备的观测数据
obsd_t_f *pdata = nullptr;
SourceData srcdata;
char l[4] = {0};
const unsigned char *p = nullptr;
while(pos < sp) {
for(int n=pos; n<pos+4; n++) { //获取message的长度
l[n-pos] = buffer[n];
}
size_t msg_len = bytes2Int(l); //将byte数组转换成一个整数,表示一个message的长度
char *msg = new char[msg_len];
for(int i=pos+4; i<pos+4+msg_len; i++) //截取一个完整的message
msg[i-(pos+4)] = buffer[i];
data.ParseFromArray(msg, msg_len); //数组序列化解析,即一个message的解析
gnssobs.n = data.n();
gnssobs.nmax = data.nmax();
pdata = new obsd_t_f[gnssobs.n];
gnssobs.data = pdata;
memset(pdata, 0, sizeof(obsd_t_f));
for(int n=0; n < data.data_size(); n++)
{
ObsData_Data obsdata= data.data(n);
ObsData_GpsTime time = obsdata.gtime();
(pdata+n)->time.time = time.time();
(pdata+n)->time.sec = time.sec();
(pdata+n)->sat = static_cast<unsigned char>(obsdata.sat());
(pdata+n)->rcv = static_cast<unsigned char>(obsdata.rcv());
}
for(int i=0; i<data.corsobs_size(); i++) {
string strcorsobs = data.corsobs(i);
setBaseData(srcdata, strcorsobs);
}
for(int i=0; i<data.corsnav_size(); i++) {
string strcorsnav = data.corsnav(i);
setNavData(srcdata, strcorsnav);
}
//---------读完整一包解算数据
abstractmain->GetContext(ctx_id)->set_sourcedata("sourcedata", srcdata); //把一包原始电文保存进context
abstractmain->GetContext(ctx_id)->set_gnssobs(gnssobs.n, gnssobs.data); //将观测数据保存进context
p = abstractmain->SolutionFlow(ctx_id); //解算
LOGI(1,"%s\r\n", p);
if (fp!=nullptr && p!=nullptr)
fprintf(fp,"%s\r\n",p);
pos = pos + 4 + msg_len; //当前位置指向下一个message
delete []msg;
}
delete []buffer;
google::protobuf::ShutdownProtobufLibrary();
}