文章目录
- ProtoBuf的使用
- 基本使用
- 指定proto3语法
- package声明符
- 定义消息(message)
- 定义消息字段
- 字段唯一编号
- 编译
- 序列化与反序列化
- 序列化与反序列化使用
ProtoBuf的使用
流程如下:
- 编写
.proto文件
,定义结构对象(message)及属性内容 - 使用 protoc 编译器编译 .proto文件,生成一系列接口代码,存放在新生成头文件和源文件中
- 依赖生成的接口,将编译生成的头文件包含进代码中,实现对 .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++ 类型 |
---|---|---|
double | double | |
float | float | |
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 |
bool | bool | |
string | 包含UTF-8和ASCII编码的字符串,长度不能超过2^32 | string |
bytes | 可包含任意的字节序列但长度不能超过2^32 | string |
变长编码[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.h
和contacts.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是将字段编码为二进制,破解成本增大,所以相对安全
以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。