当前位置: 首页 > news >正文

Protobuf的速成之旅

注意事项:本文使用Linux下的Ubuntu C/C++

一.Protobuf的安装

在安装Protobuf前需要先安装protobuf的依赖库

sudo apt-get install autoconf automake libtool curl make g++ unzip -y

Protobuf的安装链接:https://github.com/protocolbuffers/protobuf/releases

这里根据自己的环境选择安装

安装后对protobuf文件进行解压缩

如图所示,紧接着进入到文件中

进行protobuf的安装

第⼀步执⾏ autogen.sh ,

./autogen.sh 但如果下载的是具体的某⼀⻔语⾔,不需要执⾏这⼀步。

# 第⼆步执⾏ configure ,有两种执⾏⽅式,任选其⼀即可,

如下:

# 1 、 protobuf 默认安装在 /usr/local ⽬录, lib 、 bin 都是分散的 ./configure

# 2 、修改安装⽬录,统⼀安装在 /usr/local/protobuf 下

./configure --prefix=/usr/local/protobuf

make// 执⾏ 15 分钟左右

make check

 sudo make install //执行15分钟左右

若make check因服务器运行内存小而失败,可以调大swap分区至5G,若还是失败,可以跳过此步。

若你在执行./configure选择了第二步,需要进行以下操作,反之不需要 

sudo vim /etc/profile

# 添加内容如下:

#( 动态库搜索路径 ) 程序加载运⾏期间查找动态链接库时指定除了系统默认路径之外的其他路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib/

#( 静态库搜索路径 ) 程序编译期间查找动态链接库时指定查找共享库的路径

export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib/

# 执⾏程序搜索路径

export PATH=$PATH:/usr/local/protobuf/bin/

#c 程序头⽂件搜索路径

export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/protobuf/include/

#c++ 程序头⽂件搜索路径 

export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/protobuf/include/

#pkg-config 路径

export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/

source /etc/profile

再执行以下以上命令就可以使用protobuf了

通过 protoc --version检查是否安装成功,若出现version,则安装成功。

二.Protobuf的运行和编译

1.protobuf文件:文件以.proto为后缀

如:

//语法指定行
syntax="proto3";
//相当于命名域
package contacts;
message PeopleInfo{string name=1;int32 age=2;
}

基本的格式

proto3也可以换成proto2,这里我们使用proto3

package就是c++的namespace

mssage就是c++的类 class

后面的数字代表字段唯一编号,用来唯一标识字段

在这⾥还要特别讲解⼀下字段唯⼀编号的范围: 1~536,870,911(2^29-1) ,其中19000~19999不可⽤。 19000 ~19999不可⽤是因为:在Protobuf协议的实现中,对这些数进⾏了预留。如果⾮要在.proto ⽂件中使⽤这些预留标识号,例如将name字段的编号设置为19000,编译时就会报警

此外值得⼀提的是,范围为1~15的字段编号需要⼀个字节进⾏编码,16~2047内的数字需要两个字节 进⾏编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以1~15要⽤来标记出现⾮常频 繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。

Protobuf 类型C++ 类型
doubledouble
floatfloat
int32int32_t
int64int64_t
uint32uint32_t
uint64uint64_t
sint32int32_t
sint64int64_t
fixed32uint32_t
fixed64uint64_t
sfixed32int32_t
sfixed64int64_t
boolbool
stringstd::string
bytesstd::string

以上是Protobuf 与 C++类型对照表
需要注意的是对于,若是负数可以选择sint代替int,因为编码效率更高

2.编译.proto文件

   protoc  --cpp_out=.  contacts.proto           

1.  --cpp_out是选择语言

2. =后面是编译后文件的放置路径

3. contacts.proto 是被编译文件

protoc -h

若是遇到不知道可以通过以上命令进行查询。 

最后会生成以下两个文件

以下是简单的运用

#include<iostream>
#include<string>
#include"contacts.pb.h"int main()
{//protobuf是二进制存储的 contacts::PeopleInfo pinf;std::string people_str;pinf.set_age(18);pinf.set_name("张三");pinf.SerializeToString(&people_str);std::cout<<"序列化结果: "<<people_str<<std::endl;contacts::PeopleInfo dpinf;dpinf.ParseFromString(people_str);std::cout<<"反序列化结果:"<<std::endl;std::cout<<"name :"<<dpinf.name()<<'\n';std::cout<<"age :"<<dpinf.age()<<'\n';return 0;
}

g++ -o testPb contacts.cc contacts.pb.cc -std=c++11 -lprotobuf

需要和contacts.pb.cc一起编译,并且要c++11以上,要链接protobuf库 -lprotobuf

最后是运行结果:

注意:protobuf是以二进制的方式存储在文件中,所以我们序列化的结果可能跟我们想看到的内容不一样

三.protobuf .proto文件的语法与运用

本文会通过实践进行教学

跟C++里的类一样可以嵌套使用,可以讲Phone的定义写在PeopleInfo的外面,也可以也在里面,

若是写在里面,他的类型就是PeopleInfo_Phone,就跟C++中的 PeopleInfo::Phone xxx差不多。

这里的repeated就相当于数组,可以有多个值。

此外可还可以用singular字段修饰

该字段的规则是消息中可以包含该字段零次或⼀次(不超过⼀次)。proto3语法中,字段默认使⽤该 规则。

//语法指定行
syntax="proto3";
//相当于命名域
package contacts;
message Phone
{string num=1;
}
message PeopleInfo{string name=1;int32 age=2;repeated Phone phone=3;
}
message Contacts
{repeated PeopleInfo contacts=1;
}

紧接着我们用这个.proto生成的头文件编写一段代码试试:

​
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"
void AddPeopleInfo(contacts::PeopleInfo *pinfo)
{std::cout << "-------------新增联系人-------------" << std::endl;std::cout << "请输入姓名:";std::string name;std::getline(std::cin, name);std::cout << "请输入年龄:";int32_t age;std::cin >> age;pinfo->set_age(age);pinfo->set_name(name);std::cin.ignore(256, '\n');for (int i = 1;; i++){std::cout << "请输入第" << i << "个电话号码:";std::string num;std::getline(std::cin, num);contacts::Phone *ph = pinfo->add_phone();if (num.size())ph->set_num(num);elsebreak;}std::cout << "-------------添加联系人成功-------------" << std::endl;
}
int main()
{contacts::Contacts contacts;std::ifstream in("contacts.bin", std::ios::in | std::ios::binary);if (!in.is_open()){std::cerr << "file not found,create new file for you" << std::endl;}else if (!contacts.ParseFromIstream(&in)){std::cerr << "反序列失败" << std::endl;return -1;}AddPeopleInfo(contacts.add_contacts());std::fstream output("contacts.bin", std::ios::out | std::ios::trunc | std::ios::binary);if (!contacts.SerializeToOstream(&output)){std::cerr << "Failed to write contacts." << std::endl;in.close();output.close(); return -1;}in.close();output.close();return 0;
}​

可以看到,

1.对于一个类似C++中的基本数据类型,可以直接使用set_xxx()函数直接设置

2.对于类似C++中的类对象和数组,通过add_xxxx()函数,获取对应指针,然后再给指针指向的数据类使用set_xxx()赋值即可。

运行结果:

contacts.bin:

若是想将二进制形式转换为我们能看懂的文本形式可以通过:

protoc --decode=contacts.Contacts contacts.proto < contacts.bin

 --docode=跟的是package.message proto文件 ,因为默认是标准输入,所以需要将标准输入重定向到 contacts.bin

紧接着我们再写一个读取的代码:

1.可以看到通过protobuf生成的类,内置了很多函数方法如 xxx_size(), xxx.xxx()  类似的数组的&获取元素。

2.可以通过xxx.praseXXX()的方式将proto格式的数据转换为具体的proto实例对象。

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void PrintfContacts(const Contacts &contacts)
{for (int i = 0; i < contacts.contacts_size(); ++i){const PeopleInfo &people = contacts.contacts(i);cout << "-----------联系人" << i+1 << " -- -- -- -- -- --" << endl;cout<< "姓名:" << people.name() << endl;cout<< "年龄:" << people.age() << endl;int j = 1;for (const Phone &phone : people.phone()){cout << "电话 "<< j++ << ":" << phone.num() << endl;}}
}
int main()
{GOOGLE_PROTOBUF_VERIFY_VERSION;Contacts contacts;fstream input("contacts.bin", ios::in | ios::binary);if (!contacts.ParseFromIstream(&input)){cerr << "Failed to parse contacts." << endl;input.close();return -1;}PrintfContacts(contacts);
}

四.Protobuf基本类型

1.enum类型

enum与c中的enum类似,但有几个注意事项

1.0值常量必须存在,且要作为第⼀个元素。这是为了与proto2的语义兼容:第⼀个元素作为默认 值,且值为0。

2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。

3. 枚举的常量值在32位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)

4.同级(同层)的枚举类型,各个枚举类型中的常量不能重名。 

enum PhoneType
{MP=0;TEL=1;
}
enum PhoneT
{MP=0;
}

可以对于第四种情况会报错。

简单的写一个具体例子

message PeopleInfo{string name=1;int32 age=2;message Phone{string num=1;enum PhoneType{MP=0;TEL=1;}PhoneType type=2;}repeated Phone phone=3;
}
std::cout << "选择此电话类型(1、移动电话 2、固定电话) : ";int type;std::cin >> type;std::cin.ignore(256, '\n');switch (type){case 1:ph->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);break;case 2:ph->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);break;default:std::cout << "⾮法选择,使⽤默认值!" << std::endl;break;}

运行结果如下,可以看到,对于之前没有设置过的电话类型,如联系人张三,他会自动的设置默认值为0的类型。

2.Any类型

Any类型可以简单的理解为C++的template类

//语法指定行
syntax="proto3";
//相当于命名域
package contacts;//引入any类
import "google/protobuf/any.proto";message Address
{string home_addr=1;string unit_addr=2;
}message PeopleInfo{string name=1;int32 age=2;message Phone{string num=1;enum PhoneType{MP=0;TEL=1;}PhoneType type=2;}repeated Phone phone=3;google.protobuf.Any data=4;
}message Contacts
{repeated PeopleInfo contacts=1;
}
contacts::Address addr;std::cout << "请输⼊联系⼈家庭地址: ";std::string home_address;std::getline(std::cin, home_address);addr.set_home_addr(home_address);std::cout << "请输⼊联系⼈单位地址: ";std::string unit_address;std::getline(std::cin,unit_address);addr.set_unit_addr(unit_address);//mutable_data为any类型的数据开辟空间google::protobuf::Any* data=pinfo->mutable_data();//将addr转换为了any类型,并设置在开辟的空间中data->PackFrom(addr);std::cout << "-------------添加联系人成功-------------" << std::endl;
//has_data()看any类型的数据是否有设置,Is<>看data的数据是否为Address 类型if (people.has_data() && people.data().Is<Address>()) {Address address;//UnpackTo将Any类型转换为address 的Address类型people.data().UnpackTo(&address);if (!address.home_addr().empty()) {cout << "家庭地址:" << address.home_addr() << endl;}if (!address.unit_addr().empty()) {cout << "单位地址:" << address.unit_addr() << endl;}}

可以看到对于设置过address的类型会走到if里,并打印相关信息。

3.oneof类型 

作用场景:

1.若消息中有很多可选字段,并且将来同时只有⼀个字段会被设置

2.节约内存。

注意事项:

1.可选字段中的字段编号,不能与非可选字段的编号冲突。

2.不能在oneof中使用repeated字段。

3. 将来在设置oneof字段中值时,如果将oneof中的字段设置多个,那么只会保留最后⼀次设置的成员,之前设置的oneof成员会自动清除。

对于1

repeated Phone phone=3;google.protobuf.Any data=4;oneof other_contact{string qq=4;//与data的字段重复标识4string weixin=6;}

message PeopleInfo{string name=1;int32 age=2;message Phone{string num=1;enum PhoneType{MP=0;TEL=1;}PhoneType type=2;}repeated Phone phone=3;google.protobuf.Any data=4;oneof other_contact{string qq=5;string weixin=6;}
}

std::cout << "选择添加⼀个其他联系⽅式(1、qq号2、微信号) : ";int other_contact;std::cin >> other_contact;std::cin.ignore(256, '\n');if (1 == other_contact){std::cout << "请输⼊qq号: ";std::string qq;getline(std::cin, qq);pinfo->set_qq(qq);}else if (2 == other_contact){std::cout << "请输⼊微信号: ";std::string weixin;std::cout << "-------------添加联系人成功-------------" << std::endl;std::getline(std::cin, weixin);pinfo->set_weixin(weixin);}else{std::cout << "⾮法选择,该项设置失败!" << std::endl;}
 /*可以选择通过has判断if(people.has_qq()){}else if(people.has_weixin()){}else {}*/
//也可以通过other_contact_case()函数取出枚举值判断switch (people.other_contact_case()) {case PeopleInfo::OtherContactCase::kQq:cout << "qq号: " << people.qq() << endl;break;case PeopleInfo::OtherContactCase::kWeixin:cout << "微信号: " << people.weixin() << endl;break;case PeopleInfo::OtherContactCase::OTHER_CONTACT_NOT_SET:break;}

以上是通过自动生成的othercontactcase枚举类

运行结果:

4.map类型

map<key_type,value_type> xxx = N;

注意事项:

1.key_type 是除了float和bytes类型以外的任意标量类型。

2.map字段不可以⽤repeated修饰

3.map中存⼊的元素是⽆序的

message PeopleInfo{string name=1;int32 age=2;message Phone{string num=1;enum PhoneType{MP=0;TEL=1;}PhoneType type=2;}repeated Phone phone=3;google.protobuf.Any data=4;oneof other_contact{string qq=5;string weixin=6;}map<string,string> remark=7; //备注
}
for (int i = 1;; i++){std::cout << "请输入备注" << i << " 标题(只输入回车完成备注新增) : ";std::string remark_key;std::getline(std::cin, remark_key);if (remark_key.empty()){break;}std::cout << "请输入备注" << i << " 内容: ";std::string remark_value;std::getline(std::cin, remark_value);//mutable_xxxx()开辟空间,可以向该空间进行修改和设置值pinfo->mutable_remark()->insert({remark_key, remark_value});}
if (people.remark_size()){cout << "备注信息: " << endl;}//cbegin() <-> const_begin,迭代器for (auto it = people.remark().cbegin(); it != people.remark().cend();++it){cout << "    " << it->first << ": " << it->second << endl;}

相关文章:

  • 从黔西游船侧翻事件看极端天气预警的科技防线——疾风气象大模型如何实现精准防御?
  • GD32F470+CH395Q
  • LabVIEW与 IMAQ Vision 机器视觉应用
  • 深入了解linux系统—— 进程地址空间
  • STM32的智慧农业系统开发(uC/OS-II)
  • Informer 预测模型合集:新增特征重要性分析!
  • ctfshow web入门 web49
  • Spring Boot之MCP Client开发全介绍
  • Spring AI快速入门
  • php将json数据保存在MySQL中并读取
  • 基于STM32、HAL库的W25X40CLSNIG NOR FLASH存储器驱动应用程序设计
  • 【回眸】香橙派Zero2 超声波模块测距控制SG90舵机转动
  • 2025年北京市职工职业技能大赛第六届信息通信行业网络安全技能大赛初赛-wp
  • 某团小程序mtgsig,_token 生成逻辑分析
  • AWS WebRTC如何实现拉流?内部是这样实现的
  • QuecPython+Aws:快速连接亚马逊 IoT 平台
  • STM32--GPIO
  • ffmpeg录音测试
  • k8s node soft lockup (内核软死锁) 优化方案
  • <论文>(字节跳动)使用大语言模型进行时间序列理解和推理
  • 华为招聘:未与任何第三方开展过任何形式的实习合作
  • 全国铁路五一假期累计发送1.51亿人次,多项运输指标创历史新高
  • 山大齐鲁医院回应护士论文现“男性确诊子宫肌瘤”:给予该护士记过处分、降级处理
  • 青海大学常务副校长(正厅级)任延明已任省卫健委党组书记
  • 习近平给谢依特小学戍边支教西部计划志愿者服务队队员回信
  • 朝中社:美在朝鲜半岛增兵将进一步增加其本土安全不确定性