一、ProtoBuf协议
在protobuf中,协议时由一系列的消息(message)组成的,如下所示:
message 消息类型名
{
限定修饰符 字段类型 字段名=字段唯一编号;
}
message框架示例:
systax = "proto3"; //表明使用proto3语法;如果你没有指定这个,编译器会使用proto2语法;这个指定语法行必须是文件的非空非注释的第一个行
package School; //包名,类似于命名空间namespace
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: 必须字段;消息发送方,必须在发消息前设置该字段的值;消息接收方,必须能够识别该字段的意思;否则引发编解码异常,消息被丢弃。
optional:可选字段;消息发送方,可以设置或不设置该字段的值;消息接收方,如果能够识别可选字段就进行处理,无法识别则忽略该字段,其他字段正常处理;
repeated:该字段可以包含0~n个元素,其特性同optional,但每次可以包含多个值,可以看作一个数组。
②数据类型
ProtoBuf定义了一套基本数据类型,几乎都可以映射到C++\Java等语言的基础数据类型。
protobuf数据结构 | 描述 | 打包 | C++语言映射 |
bool | 布尔类型 | 1字节 | bool |
float | 32位浮点数 | float | |
int32 | 32位整数 | int | |
int 64 | 64位整数 | int | |
string | 只能处理ASCII字符,使用中文的化需要转换成utf_8,,不然回报错 | std::string | |
bytes | 用于处理多字节的语言字符,如中文 | std::string | |
message | 可以包含一个用户自定义的消息类型 | object of class | |
RTKEY | RTDB_KEY_TYPE | ||
③字段名称
由字母、数字和下划线组成;建议使用以下划线分割的驼峰式,如first_name而不是firstName。
④字段编码值
- 通信双方通过该值相互识别对方的字段。相同的编码值,其限定修饰符和数据类型必须相同;编码值取值范围为1~2^32,其中1~15的编码时间和空间效率都是最高的,因此把要经常传递的值的字段编码设置为1~15之间的值。
- 消息中的字段编码值无需联系,不能在同一个消息中有字段包含相同的编码值。
- 新增消息的字段建议全部使用optional或者repeated,若使用required,需要全网统一升级。
⑤字段默认值
消息发送方:required数据类型,传递默认值到对端。
消息接收方:没收到optional字段,设置为默认值。
optional int32 num=1 [default=0]
数据类型 | 默认值 |
strings | 空string |
bytes | 空bytes |
bools | false |
数值类型 | 0 |
枚举类型 | 第一个值 |
二、编译
使用proto编译器,编译.proto文件,编译完成后自动生成.ph.cc和.pb.h文件;
命令格式:
protoc [--proto_path=IMPORT_PATH] --cpp_out=OUT_DIR path/to/file.proto
/*
protoc:Protocol Buffer提供的命令行编译工具。
--proto_path:指定被编译的.proto文件所在目录,可写为 -I IMPORT_PATH。不指定此参数,在当前目录进行搜索;当某个.proto文件import其他.proto文件时,或需要编译的.proto文件不在当前目录,需要用-I来指定搜索目录。
--cpp_out:指编译后的文件为C++文件。
OUT_DIR:指编译后生成文件的目标路径
*/
eg1:
protoc -cpp_out = .contacts.proto //在当前contacts.proto文件目录下执行
proto -I ./fast_start --cpp_out = ./fast_start/contacts.proto //在contacts.proto文件父目录下执行。
其中包含了以下内容:
- 编辑器会针对于每个 .proto ⽂件⽣成 .h 和 .cc ⽂件,分别⽤来存放类的声明与类的实现。
- 对于每个message,都会⽣成⼀个对应的消息类。
- 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的⽅法。
三、Protobuf中数据类型的读写
1.非嵌套
syntax = "proto3";
package tutorial;
message Person
{
string name = 1;
int32 id = 2;
string email = 3;
}
tutorial::Person person;
person.set_name("Obama");
person.set_id(1234);
person.set_email("1234@qq.com");
2.嵌套
syntax = "proto3";
package tutorial;
message PhoneNumber
{
string number = 1;
PhoneType type = 2;
}
message Person
{
string name = 1;
int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
repeated PhoneNumber phones = 4;
}
对PhoneNumber中number成员的赋值
1)使用set_allocated_传入指针
(1)一次赋值进来
tutorial::Person person;
tutorial::PhoneNumber *p_n=new tutorial::PhoneNumber;
p_n->set_number(100);
p_n->set_type();//此处是一层枚举类,若是多层消息类则一直.进去到最后(一般A.proto中的值set进B.proto中的某个值)
person.set_allocated_phone_number(p_n)//一次赋值进来
(2)一个个分别赋值
tutorial::Person person;
person.set_allocated_phone_number()->set_number(100);
2)使用mutable_
tutorial::Person person;
person.mutable_phone_number()->set_number(100);
3)也可以copyFrom 方法使用结构体结合mutable_赋值
tutorial::Person person;
tutorial::PhoneNumber p_n;
p_n->set_number(100);
p_n->set_type();
person.mutable_phone_number()->CopyFrom(p_n);
cout<<person.DebugString()<<endl;
5)当消息内容为Map类型时
package protoTest;
message DemoMsg
{
map<int32,string> a = 1;
}
map 类型成员赋值需通过 mutable_xxx 方法进行:
protoTest::DemoMsg msg;
msg.mutable_a()->insert({ 1,"aaaaa" });
msg.mutable_a()->insert({ 2,"bbbbb" });
msg.mutable_a()->insert({ 3,"ccccc" });
cout << msg.DebugString() << endl;
map成员读取:
protoTest::DemoMsg msg;
msg.mutable_a()->insert({ 1,"aaaaa" });
msg.mutable_a()->insert({ 2,"bbbbb" });
auto map = msg.mutable_a();
if (map->contains(1))
{
cout << map->at(1) << endl;
}
6)读取RTKEY类型数据
//读取RTKEY类型数据
RTDB_KEY_TYPE rtdb_temp;
rtdb_temp.key_id1=st_id.id1();
rtdb_temp.key_id2=st_id.id2();
四.repeated成员操作
syntax = "proto3";
package tutorial;
message Person
{
repeated PhoneNumber phones = 4;
}
message PhoneNumber
{
string number = 1;
PhoneType type = 2;
}
1.赋值
add_<参数名>(参数值)
eg1.
tutorial::Person person;
tutorial::PhoneNumber *phones=person.add_phones();
phones->set_number(100);
eg2.
message Person {
required int32 age = 1;
required string name = 2;
}
message Family {
repeated Person person = 1;
}
Family family;
Person* person;
// 添加一个家庭成员,John
person = family.add_person();
person->set_age(25);
person->set_name("John");
// 添加一个家庭成员,Lucy
person = family.add_person();
person->set_age(23);
person->set_name("Lucy");
// 添加一个家庭成员,Tony
person = family.add_person();
person->set_age(2);
person->set_name("Tony");
// 显示所有家庭成员
int size = family.person_size();
cout << "这个家庭有 " << size << " 个成员,如下:" << endl;
for(int i=0; i<size; i++)
{
Person psn = family.person(i);
cout << i+1 << ". " << psn.name() << ", 年龄 " << psn.age() << endl;
}
2.取出
xxx_size()获取repeated标记的成员个数。
protoTest::DemoMsg msg;
msg.add_a(0.2);
msg.add_a(0.5);
int size = msg.a_size();
for (int i = 0; i < size; ++i)
{
cout << msg.a(i) << " "; //打印数组a中的全部元素
}
2.读取
④.元素删除
使用 mutable_xxx方法获取成员的指针,可以删除指定位置元素。
protoTest::DemoMsg msg;
msg.add_a(0.2);
msg.add_a(0.5);
msg.mutable_a()->erase(msg.mutable_a()->begin()); //删除了数组a中0.2的值
cout << msg.DebugString() << endl;
四.import导入其他proto文件定义的消息
开发一个项目的时候,通常很多消息定义都写在一个proto文件,不方便维护,通常会将消息定义写在不同的proto文件中,在需要的时候可以通过import导入其他proto文件定义的消息。
eg.
保存文件:result.proto
syntax = "proto3";
message Result // Result消息定义
{
string url = 1;
string title = 2;
repeated string snippets = 3; // 字符串数组类型
}
保存文件:serach_response.proto
syntax = "proto3";
import "result.proto"; // 导入Result消息定义
message SearchResponse // 定义SearchResponse消息
{
repeated Result results = 1; // 使用导入的Result消息
}