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

【ProtoBuf】基础使用与编译

文章目录

  • ProtoBuf的使用
  • 基本使用
    • 指定proto3语法
    • package声明符
    • 定义消息(message)
    • 定义消息字段
    • 字段唯一编号
  • 编译
    • 序列化与反序列化
    • 序列化与反序列化使用

ProtoBuf的使用

在这里插入图片描述
流程如下:

  1. 编写 .proto文件,定义结构对象(message)及属性内容
  2. 使用 protoc 编译器编译 .proto文件,生成一系列接口代码,存放在新生成头文件和源文件中
  3. 依赖生成的接口,将编译生成的头文件包含进代码中,实现对 .proto文件中定义的字段进行设置和获取,和对 message 对象进行序列化和反序列化

基本使用

通过实现一个简单的通讯录项目作例子

文件规范:创建 .proto文件时,文件命名应该使用全小写字母命名,多个字母之间使用_连接,如lower_snake_case.proto

添加注释:.proto文件添加注释,可以使用///*...*/

指定proto3语法

Protocol Buffer语言版本3,简称 proto3。proto3简化了 Protocol Buffer语言,既易于使用,又可以子啊更广泛的编程语言中使用。允许使用 C++,Java,Python等多种语言生成protocol buffer代码
在 .proto文件中,要使用syntax="proto3";来指定文件语法为proto3,并且必须写在除去注释内容的第一行。如果没有指定,编译器会使用proto2语法

//注释1
syntax = "proto3";//除去注释的第一行

package声明符

package 是一个可选的声明符,能表示 .proto文件的命名空间,经protoc编译器会生成为namespace,在项目中要有唯一性。作用是避免定义的消息出现冲突

syntax = "proto3";//语法规则
package contacts;//命名空间

定义消息(message)

消息(message):要定义的结构化对象,经由 protoc编译器生成为class

//.proto文件中定义一个消息类型的格式为:
message 消息类型名{
}
//消息类型命名规范:使用驼峰命名法,首字母大写 

示例:

syntax = "proto3";
package contacts;
//定义联系人信息
message PeopleInfo{}

定义消息字段

在message中我们可以定义其属性字段,字段定义格式为:字段类型 字段名 = 字段唯一编号;

  • 字段名称命名规范:全小写字母,多个字母之间使用_连接
  • 字段类型分为:标量数据类型特殊类型(包括枚举、其他消息类型等)
  • 字段唯一编号:用来识别字段,一旦开始使用就不能再改变

下表展示标量数据类型,及经编译后自动生成的类中与之对应的类型

.proto 类型说明C++ 类型
doubledouble
floatfloat
int32使用变长编码[1]。负数的编码效率较低——若字段可能为负值,应使用sint32代替int32
int64使用变长编码[1]。负数的编码效率较低——若字段可能为负值,应使用sint64代替int64
uint32使用变长编码[1]uint32
uint64使用变长编码[1]uint64
sint32使用变长编码[1]。符号整型,负值的编码效率高于常规的int32类型int32
sint64使用变长编码[1]。符号整型,负值的编码效率高于常规的int64类型int64
fixed32定长4字节,若值常大于2 ^ 28则会比uint32更高效uint32
fixed64定长8字节,若值常大于2 ^ 56则会比uint64更高效uint64
sfixed32定长4字节int32
sfixed64定长8字节int64
boolbool
string包含UTF-8和ASCII编码的字符串,长度不能超过2^32string
bytes可包含任意的字节序列但长度不能超过2^32string

变长编码[1]:经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数

PeopleInfo添加字段

syntax = "proto3";
package contacts;message PeopleInfo{string name = 1;int32 age = 2;
}

字段唯一编号

字段唯一编号的范围:

1 ~ 2^29 - 1(536870911),其中19000 ~ 19999不可用
不可用的部分是Protobuf协议对这些数进行了预留,如果使用了这些编号,在编译时会报警

//例将name字段编号定位19000
string name = 19000
Field numbers 19000 through 19999 are reserved for protobuf implementation

PS:范围1 ~ 15的字段编号只需要一个字节进行编码,16 ~ 2047 内的数字需要两个字节编码。编码后的字节不仅包含了编号,还有字段类型。所以可以使用1 ~ 15用来标记出现非常频繁的字段

编译

编译命令格式如下:

protoc [--proto_path=import_path] -cpp_out=dst_dir path/file.protoprotoc:是Protocol Buffer 提供的命令行编译工具
proto_path:指定 被编译的.proto文件所在目录,可多次指定。可简写为 -I
import_path:如果不指定,则默认在当前目录搜索
--cpp_out=:指示编译后的文件为C++文件
out_dir:编译后生成文件的目标路径
path/file.proto:要编译的.proto文件

示例:如果.proto文件在当前目录下,编译后的文件也直接生成在当前目录

protoc --cpp_out=. ./contacts.proto1. 因为编译文件在当前目录,可以不指明搜索路径,省略 -I
2. --cpp_out=.  生成C++文件,且生成在当前目录
3. ./contacts.proto   指明编译的.proto文件

示例:如果.proto文件在上级目录,将编译后的文件生成在当前目录的dir子文件夹中

protoc -I ../ --cpp_out=./dir ../contacts.proto1. -I ../   		  指明搜索路径是上级目录
2.  --cpp_out=./dir   指明输出C++文件到dir文件夹中
3. ../contacts.proto  指明要编译的.proto文件 

对于编译后生成的contacts.pb.hcontacts.pb.cc包含以下内容

  • 对于每个message,都会生成一个对应的消息类
  • 在消息类中,编译器为每个字段提供了获取和设置方法,以及一些能够操作字段的方法
  • .h文件为声明,.cc文件为实现
  • 字段的获取方法,名称同小写字段名相同,如字段名为name,就通过name()获取;设置方法名称以 set_开头,如set_name()
  • 每个字段都有一个 clear_(),可以将字段重新设置为 empty状态

序列化与反序列化

.proto文件编译生成的类都继承自MessageLite,序列化方法和反序列化方法也是从中继承

class MessageList
{
public://序列化:bool SerializeToOstream(string* output) const;bool SerializeToArray(string* output) const;bool SerializeToString(string* output) const;//反序列化:bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化动作bool ParseFromArray(const void* data, int size);bool ParseFromString(const string& data);
};
  • 序列化的结果为二进制字节序列,而非文本格式
  • 以上三种序列化方法没有本质区别,只是序列化后的输出格式不同,可以供不同应用场景使用
  • 序列化的API函数均为const成员函数,因为序列化不会改变类对象的内容,而是将序列化的结果保存到函数入参指定的地址中
  • 详细message API 可以参见 完整列表

序列化与反序列化使用

创建⼀个测试文件main.cc,实现:

  • 对⼀个联系⼈的信息进行序列化,并将结果打印出来
  • 对序列化后的内容反序列,解析出联系⼈信息并打印出来

main.cc

#include<iostream>
#include"contacts.pb.h"//引⼊编译⽣成的头⽂件
usingnamespacestd;
int main() 
{string people_str;{// .proto⽂件声明的package,通过protoc编译后,会为编译⽣成的C++代码声明同名的命名空间//其范围是在.proto⽂件中定义的内容contacts::PeopleInfo people;people.set_age(20);people.set_name("张珊");//调⽤序列化⽅法,将序列化后的⼆进制序列存⼊string中if(!people.SerializeToString(&people_str))cout <<"序列化联系⼈失败."<< endl;//打印序列化结果cout <<"序列化后的people_str: "<< people_str << endl;}{contacts::PeopleInfo people;//调⽤反序列化⽅法,读取string中存放的⼆进制序列,并反序列化出对象if(!people.ParseFromString(people_str)) {cout <<"反序列化出联系⼈失败."<< endl;}//打印结果cout <<"Parse age: "<< people.age() << endl;cout <<"Parse name: "<< people.name() << endl;}
}

用如下命令编译main.cc,生成可执行程序:

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

需要链接protobuf动态库

相对于xml和JSON,因为protobuf是将字段编码为二进制,破解成本增大,所以相对安全


以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述


http://www.mrgr.cn/news/48784.html

相关文章:

  • C# udp通信测试助手-点对点-网络断开检测
  • 在SpringBoot+VUE中 实现登录-RSA的加密解密
  • Spring Boot 项目中 Redis 与数据库性能对比实战:从缓存配置到时间分析,详解最佳实践
  • 【Java知识】java进阶-手撕动态代理
  • 【AI论文精读12】RAG论文综述2(微软亚研院 2409)P4-隐性事实查询L2
  • 16路舵机控制芯片lu9685使用技巧
  • 数据结构-5.4.二叉树的性质
  • 认识C++的变量与整型
  • threejs-补间动画Tween应用
  • [Linux] Linux 进程程序替换
  • 【C++】关联式容器——map和set的使用
  • 可观察性的三大支柱:统一日志、指标和跟踪
  • 衡石分析平台系统管理手册-智能运维之系统日志
  • SpringBoot接口异常:Request header is too large
  • MySQL表的操作
  • Git Commit 规范
  • 对偶范数(Dual Norm)
  • Java-学生管理系统[初阶]
  • uniapp-小程序开发0-1笔记大全
  • sklearn pipeline