在protobuf中,协议是由一系列的消息(message)
组成的,如下所示:
systax = "proto3"; //表明使用proto3语法;如果你没有指定这个,编译器会使用proto2语法;这个指定语法行必须是文件的非空非注释的第一个行
package school; //包名,类似于模块
message student { //消息,类似于类
required string name = 1 [default="张三"];
optional int32 chinese = 2 [default=0];
optional int32 math = 3 [default=0];
optional int32 english = 4 [default=0];
}
message teacher {
required strint name = 1;
optional string class = 2;
optional string object = 3;
}
message hengshuizhongxue {
repeated student student = 1; //message内可以嵌套message
repeated teacher teachar = 2;
}
字段格式:
限定修饰符① | 数据类型② | 字段名称③ = 字段编码值④ | 字段默认值⑤
①. 限定修饰符 required
| optional
| repeated
required:表示是一个必须字段
,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。
optional:表示是一个可选字段
,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。
repeated:表示该字段可以包含0~n个元素
, 其特性和optional一样,但是每一次可以包含多个值,可以看作是一个数组
。
②. 数据类型
protobuf定义了一套基本数据类型,几乎都可以映射到c \java等语言的基础数据类型。
protobuf 数据结构 | 描述 | 打包 | c 语言映射 |
---|---|---|---|
bool | 布尔类型 | 1字节 | bool |
double | 64浮点数 | n | double |
float | 32浮点数 | n | float |
int32 | 32位整数 | n | int |
uint32 | 无符号32位整数 | n | unsigned int |
int64 | 64位整数 | n | __int64 |
uint64 | 64位无整数 | n | unsigned __int64 |
sint32 | 32位整数,处理负数效率更高 | n | int32 |
sint64 | 64位整数,处理负数效率更高 | n | __int64 |
fixed32 | 32位无符号整数 | 4 | unsigned int32 |
fixed64 | 64位无符号整数 | 8 | unsigned __int64 |
sfixed32 | 32位整数,能以更高的效率处理负数 | 4 | unsigned int32 |
sfixed64 | 64位整数 | 8 | unsigned __int64 |
string | 只能处理ascii字符 | n | std::string |
bytes | 用于处理多字节的语言字符,如中文 | n | std::string |
enum | 可以包含一个用户自定义的枚举类型uint32 | n(uint32) | enum |
message | 可以包含一个用户自定义的消息类型 | n | object of class |
n:表示打包的字节并不是固定的,而是根据数据的大小或者长度决定的
③. 字段名称
字段名称的命名与c、c 、java等语言的变量命名方式几乎是相同的:字母、数字和下划线组成
protobuf建议字段的命名采用以下划线分割的驼峰式,例如:first_name 而不是firstname
④. 字段编码值
有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同,编码值的取值范围为1~2^32
其中1~
15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1~
15), protobuf 建议把经常要传递的值把其字段编码设置为1-15之间的值。
消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。
建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不实用required。如果使用了required,需要全网统一升级,如果使用optional或者repeated可以平滑升级。
⑤. 字段默认值
当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。 对于optional字段,如果没有接收到optional字段,则设置为默认值。
对于strings,默认是一个空string
对于bytes,默认是一个空的bytes
对于bools,默认是false
对于数值类型,默认是0
//demo.proto 协议格式文件
syntax='proto3'
package=demo
message data {
optional int32 x = 1;
optional string str = 2;
repeated int32 d = 3;
}
2.1、类成员变量的访问
- 获取成员变量:直接采用使用成员变量名(全部为小写);
- 设置成员变量:使用成员变量名前加
set_
的方法
//使用message
#include
#include
demo::data data;
data.set_x(20); //设置成员变量
qdebug()<
对于普通成员变量(required和optional)
- 提供
has_
方法判断变量值是否被设置;
- 提供
clear_
方法清除设置的变量值 ;
//使用message
#include
#include
demo::data data;
data.set_x(20); //设置成员变量
qdebug()<
对于string类型
- 提供了多种
set_
方法,其参数不同;
- 提供了一个
mutable_
方法,返回变量值的可修改指针 ;
//使用message
#include
#include
demo::data data;
data.set_str(20); //设置成员变量
std::string* mutable_str(); //返回str变量值的可修改指针
对于repeated变量
_size
方法:返回变量的长度;
- 通过下脚标访问其中的数据成员组;
- 通过下脚标返回其中的成员的
mutable_
的方法
_add
方法:增加一个成员
//使用message
#include
#include
demo::data data;
for(int i=0; i<10; i )
{
data.d_add(i); //向d中添加成员
}
for(int i=0; i
3.1、序列化和反序列化有什么用?
序列化和反序列化主要用在保存数据结构上,保存数据很简单,各种形式都可以,例如txt,但是如果想把数据恢复成原先的数据结构就没那么简单了。
例如:下面是一个学生的结构体
struct student {
qstring name;
qstring class;
long int stu_id;
float chinese;
float math;
float english;
}
假如把三年一班的学生记录为一个结构体数组struct student class_3_1[max_length];
;
把它存为.txt文件
,如果想把它恢复成struct student class_3_1[max_length];
就得自己解析文件,特别麻烦。
如果采用序列化可以把数据结构序列化为二进制数据进行存储,反序列化可以把存储的二进制数据再次恢复成之前的数据结构,很方便使用。
3.2、序列化
//文件后缀可以自定
filename = qfiledialog::getsavefilename(0, qobject::tr("protobuf序列化"),currentpath,qobject::tr("testdata(*.td)"));
if (!filename.isempty())
{
if (!filename.endswith(".td"))
{
filename = ".td";
}
qfile file(filename);
if(file.open(qiodevice::writeonly))
{
// data 是一个 demo::data message对象
int nlength = data->bytesize();
char* pbuf = new char[nlength];
data->serializepartialtoarray(pbuf,nlength); //序列化
if(nlength == file.write(pbuf,nlength))
{
qdebug()<<"save_sucess";
}
else
{
qdebug()<<"save_fail";
}
}
file.close();
}
3.3、反序列化
filename = qfiledialog::getopenfilename(0, qobject::tr("读取参考曲线数据 "),scurpath,qobject::tr("referenceline(*.rl)"));
demo::data data_1;
if (!filename.isempty())
{
qfile file(filename);
if(file.open(qiodevice::readonly))
{
qbytearray array_para = file.readall();
int nlen = array_para.length();
// data 是一个 demo::data message对象
if(!data_1->parsepartialfromarray(array_para.data(),nlen)) //反序列化
{
qdebug()<<"load_fail";
}
else
{
qdebug()<<"load_success";
int x = data_1->x();
qstring str = qstring::fromutf8(data_1->str().data());
qvector d;
for(int i=0; id_size(); i )
{
d.push_back(data_1->d(i));
}
}
}
file.close();
}