文章目录
- 字段规则
- 消息类型定义与使用
- 序列化写入文件
- hexdump工具
- 反序列化读取文件
- decode命令选项
- enum类型
- 设置电话类型
- Any类型
- 设置地址信息
- oneof类型
- 设置其他联系人信息
- map类型
- 添加备注信息
- 默认值
- 更新消息
- 更新规则
- 未知字段
- 输出未知字段消息
- option选项
- 常用选项
本章代码仓库:proto3语法
本篇文章实现内容:
- 基于上篇文章的通讯录进行需求升级
- 不再输出联系人序列化结果,将通讯录序列化内容写入文件
- 从文件中解析内容,并输出
- 新增联系人属性
字段规则
repeated
:消息中可以包含该字段任意多次,其中重复值的顺序会被保留,可以理解为定义一个数组。singular
:消息中可以包含该字段零次或一次,proto3
语法中,字段默认使用次规则
消息类型定义与使用
直接定义:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
repeated string phone_nums = 3; //可能有多个电话
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
定义多个message:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
message Phone
{
string number = 1;
}
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
repeated Phone phone = 3;
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
嵌套定义:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
message Phone
{
string number = 1;
}
repeated Phone phone = 3;
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
分文件:
phone.proto
:
syntax = "proto3";
package phone;
message Phone
{
string number = 1;
}
contacts.proto
:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
import "phone.proto";
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
repeated phone.Phone phone = 3;
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
序列化写入文件
读取流,并进行反序列化:
bool ParseFromIstream(std::istream* input);
进行反序列,化并将结果写入流:
bool SerializeToOstream(std::ostream* output) const;
清除缓冲区内容:
- 清除到256停止
- 遇到
\n
停止
cin.ignore(256, '\n');
添加联系人,返回一个开辟好的空间,然后在这个空间上进行操作(设置各种字段):
contacts::PeopleInfo* Contacts::add_contacts()
#include<iostream>
#include<fstream>
#include"contacts.pb.h"
using namespace std;
void AddPeopleInfo(contacts::PeopleInfo* people)
{
cout << "--------新增联系人--------" << endl;
cout << "输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); //清楚输入缓冲区
cout << "输入联系人电话: ";
for(int i = 1; ; i++)
{
cout << "输入联系人电话" << i << "(输入回车表明结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contacts::PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
}
cout << "--------添加成功--------" << endl;
}
int main()
{
contacts::Contacts contacts;
//读取本地存在联系人文件
fstream input("contacts.bin", ios::in | ios::binary);
if(!input)
{
cout << "contacts.bin not find, create new file" << endl;
}
else if(!contacts.ParseFromIstream(&input))
{
cerr << "parse error" << endl;
input.close();
return -1;
}
//向通讯录添加联系人
//将add_contacts的返回值作为入参函数
AddPeopleInfo(contacts.add_contacts());
//通讯录写入本地文件
fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
if(!contacts.SerializeToOstream(&output))
{
cerr << "write error" << endl;
input.close();
output.close();
return -1;
}
cout << "write success" << endl;
input.close();
output.close();
return 0;
}
Tips:
可以在
main()
函数开头和结尾添加宏和关闭函数:int main() { //如果有安装不同版本protobuf,链接到与版本不匹配的时候, 程序执行直接终止 //每个*.pb.cc启动的时候, 都会自动调用这个宏 GOOGLE_PROTOBUF_VERIFY_VERSION; //... //... //... //程序结束的时候, 删除protobuf全局对象 //如果使用了内存泄漏检查程序,该程序需要释放每个最后对象 google::protobuf::ShutdownProtobufLibrary(); }
hexdump工具
上面的程序生成了二进制文件,采用hexdump
工具,可以将二进制文件进行转换。
-C
表示生成规范的16进制和ASCII码
hexdump -C contacts.bin
反序列化读取文件
取出元素:
contacts::PeopleInfo& Contacts::contacts(int index) const
#include<iostream>
#include<fstream>
#include"contacts.pb.h"
using namespace std;
void PrintContacts(const contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number() << endl;
}
}
}
int main()
{
contacts::Contacts contacts;
//读取本地存在通讯录文件
fstream input("contacts.bin", ios::in | ios::binary);
if(!contacts.ParseFromIstream(&input))
{
cerr << "parse error" << endl;
input.close();
return -1;
}
//输出通讯录
PrintContacts(contacts);
}
decode命令选项
查看所有命名选项:
protoc -h
--decode
选项可以将二进制消息转换成文本格式
protoc --decode=contacts.Contacts contacts.proto < contacts.bin
--decode=contacts.Contacts
表明解析的是contacts
命名空间下的Contacts
< contacts.bin
表明标准输入重定向到contacts.bin
文件
如果是utf-8汉字,则转换成八进制格式
enum类型
- 零值常量必须存在,且必须作为枚举类型第一个枚举值
- 枚举常量值必须在32位整数范围内(负值不生效)
- 同级枚举类型下,如果有相同常量名称,会引发冲突
syntax = "proto3";
enum PhoneType
{
//常量名称建议采用大小
MP = 0;
TEL = 1;
}
嵌套定义:
syntax = "proto3";
message Phone
{
enum PhoneType
{
//常量名称建议采用大小
MP = 0;
TEL = 1;
}
}
如果有个.proto
文件枚举名称冲突,如果声明了package命名空间,则不影响
更新proto文件:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
message Phone
{
string number = 1;
enum PhoneType
{
MP = 0; //移动电话
TEL = 1; //固定电话
}
PhoneType type = 2;
}
repeated Phone phone = 3;
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
重新编译:
设置电话类型
write.cc
:
void AddPeopleInfo(contacts::PeopleInfo* people)
{
cout << "--------新增联系人--------" << endl;
cout << "输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); //清楚输入缓冲区
for(int i = 1; ; i++)
{
cout << "输入联系人电话" << i << "(输入回车表明结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contacts::PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
cout << "输入电话类型(1.移动电话 2.固定电话): ";
int type;
cin >> type;
cin.ignore(256,'\n');
switch (type)
{
case 1:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
break;
case 2:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "选择有误" << endl;
break;
}
}
cout << "--------添加成功--------" << endl;
}
read.cc
:
void PrintContacts(const contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
}
}
这里的张三和lisa之前并没有设置电话类型,会默认设置枚举值常量为零的常量
Any类型
Any
类型是protobuf
事先定义好的一个message
Any
类型是一个泛型类型,可以存储任意的消息类型
更新proto
文件:
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
import "google/protobuf/any.proto";
message Address
{
string home_address = 1; //家庭地址
string unit_address = 2; //单位地址
}
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
message Phone
{
string number = 1;
enum PhoneType
{
MP = 0; //移动电话
TEL = 1; //固定电话
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
提供的一些方法:
设置地址信息
新增地址信息其实是往Any
类型的data
字段设置内容。
而Any
是泛型类型,所以先要定义一个Address
对象,然后将Address
对象转换成Any
对象。
更新write.cc
:
void AddPeopleInfo(contacts::PeopleInfo* people)
{
cout << "--------新增联系人--------" << endl;
cout << "输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); //清楚输入缓冲区
for(int i = 1; ; i++)
{
cout << "输入联系人电话" << i << "(输入回车表明结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contacts::PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
cout << "输入电话类型(1.移动电话 2.固定电话): ";
int type;
cin >> type;
cin.ignore(256,'\n');
switch (type)
{
case 1:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
break;
case 2:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "选择有误" << endl;
break;
}
}
contacts::Address address;
cout << "输入联系人家庭地址: ";
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout << "输入联系人单位地址: ";
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);
//Address对象转换成Any
people->mutable_data()->PackFrom(address);
cout << "--------添加成功--------" << endl;
}
更新read.cc
:
void PrintContacts(const contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
if(people.has_data() && people.data().Is<contacts::Address>())
{
contacts::Address address;
//转换成address对象 将内容输出到address里
people.data().UnpackTo(&address);
if(!address.home_address().empty())
{
cout << "联系人家庭地址: " << address.home_address() << endl;
}
if(!address.unit_address().empty())
{
cout << "联系人单位地址: " << address.unit_address() << endl;
}
}
}
}
oneof类型
从多个字段中选择一个字段,可以采用oneof
进行约束。
//语法指定行
syntax = "proto3";
//定义声明空间
package contacts;
import "google/protobuf/any.proto";
message Address
{
string home_address = 1; //家庭地址
string unit_address = 2; //单位地址
}
//定义联系人message
message PeopleInfo
{
//标识字段编号
string name = 1;
int32 age = 2;
message Phone
{
string number = 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 wechat = 6;
}
}
//通讯录message
message Contacts
{
repeated PeopleInfo contacts = 1;
}
oneof
里面的字段编号不能和PeopleInfo
里面的编号冲突,相当于是同级字段oneof
中不能使用repeated
修饰oneof
字段设置多次,只会保留最后一次设置的值
oneof
字段设置的多个类型,会转换成枚举类型:
设置其他联系人信息
更新write.cc
:
{
cout << "--------新增联系人--------" << endl;
cout << "输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); //清楚输入缓冲区
for(int i = 1; ; i++)
{
cout << "输入联系人电话" << i << "(输入回车表明结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contacts::PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
cout << "输入电话类型(1.移动电话 2.固定电话): ";
int type;
cin >> type;
cin.ignore(256,'\n');
switch (type)
{
case 1:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
break;
case 2:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "选择有误" << endl;
break;
}
}
contacts::Address address;
cout << "输入联系人家庭地址: ";
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout << "输入联系人单位地址: ";
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);
//Address对象转换成Any
people->mutable_data()->PackFrom(address);
cout << "选择要添加的其他联系方式(1.QQ 2.微信):";
int other_contact;
cin >> other_contact;
cin.ignore(256, '\n');
if(other_contact == 1)
{
cout << "输入联系人QQ号码:";
string qq;
getline(cin ,qq);
people->set_qq(qq);
}
else if(other_contact == 2)
{
cout << "输入联系人微信号:";
string wechat;
getline(cin, wechat);
people->set_wechat(wechat);
}
else
{
cout << "输入非法, 未成功设置其他联系方式" << endl;
}
cout << "--------添加成功--------" << endl;
}
更新read.cc
:
void PrintContacts(const contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
if(people.has_data() && people.data().Is<contacts::Address>())
{
contacts::Address address;
//转换成address对象 将内容输出到address里
people.data().UnpackTo(&address);
if(!address.home_address().empty())
{
cout << "联系人家庭地址: " << address.home_address() << endl;
}
if(!address.unit_address().empty())
{
cout << "联系人单位地址: " << address.unit_address() << endl;
}
}
switch(people.other_contact_case())
{
case contacts::PeopleInfo::OtherContactCase::kQq:
cout << "联系人qq: " << people.qq() << endl;
break;
case contacts::PeopleInfo::OtherContactCase::kWechat:
cout << "联系人微信: " << people.wechat() << endl;
break;
default:
break;
}
}
}
map类型
map
就是一个映射字段map<key_type, value_type> map_field = N;
key_type
除了float
和bytes
类型以外任意标量类型;value_type
可以是任意类型- 不能用
repeated
修饰 - 存入的元素是无需的
在contacts.proto
添加:
map<string, string> remark = 7; //备注
添加备注信息
更新write.cc
:
void AddPeopleInfo(contacts::PeopleInfo* people)
{
cout << "--------新增联系人--------" << endl;
cout << "输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); //清楚输入缓冲区
for(int i = 1; ; i++)
{
cout << "输入联系人电话" << i << "(输入回车表明结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contacts::PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
cout << "输入电话类型(1.移动电话 2.固定电话): ";
int type;
cin >> type;
cin.ignore(256,'\n');
switch (type)
{
case 1:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
break;
case 2:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "选择有误" << endl;
break;
}
}
contacts::Address address;
cout << "输入联系人家庭地址: ";
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout << "输入联系人单位地址: ";
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);
//Address对象转换成Any
people->mutable_data()->PackFrom(address);
cout << "选择要添加的其他联系方式(1.QQ 2.微信):";
int other_contact;
cin >> other_contact;
cin.ignore(256, '\n');
if(other_contact == 1)
{
cout << "输入联系人QQ号码:";
string qq;
getline(cin ,qq);
people->set_qq(qq);
}
else if(other_contact == 2)
{
cout << "输入联系人微信号:";
string wechat;
getline(cin, wechat);
people->set_wechat(wechat);
}
else
{
cout << "输入非法, 未成功设置其他联系方式" << endl;
}
for(int i = i; ; i++)
{
cout << "输入备注" << i << "标题(输入回车完成新增):";
string remark_key;
getline(cin, remark_key);
if(remark_key.empty())
{
break;
}
cout << "输入备注" << i << "内容:";
string remark_value;
getline(cin, remark_value);
people->mutable_remark()->insert({remark_key, remark_value});
}
cout << "--------添加成功--------" << endl;
}
更新read.cc
:
void PrintContacts(const contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
if(people.has_data() && people.data().Is<contacts::Address>())
{
contacts::Address address;
//转换成address对象 将内容输出到address里
people.data().UnpackTo(&address);
if(!address.home_address().empty())
{
cout << "联系人家庭地址: " << address.home_address() << endl;
}
if(!address.unit_address().empty())
{
cout << "联系人单位地址: " << address.unit_address() << endl;
}
}
switch(people.other_contact_case())
{
case contacts::PeopleInfo::OtherContactCase::kQq:
cout << "联系人qq: " << people.qq() << endl;
break;
case contacts::PeopleInfo::OtherContactCase::kWechat:
cout << "联系人微信: " << people.wechat() << endl;
break;
default:
break;
}
if(people.remark_size())
{
cout << "备注信息: " << endl;
}
for(auto it = people.remark().cbegin(); it != people.remark().cend(); it++)
{
cout << " " << it->first << ": " << it->second << endl;
}
}
}
默认值
反序列化时,如果反序列化的二进制序列不包含某个字段,则会采用默认值填充:
-
字符串:默认空字符串
-
字节:默认空字节
-
布尔值:默认false
-
数值类型:默认0
-
枚举:默认第一个枚举值,必须为0
-
消息字段:取决于依赖的语言
-
设置了
repeated
字段:默认为空 -
对于消息字段、oneof字段、any字段,C++和Java都有
has_
方法检测该字段是否被设置对于标量数据类型,在
proto3
语法当中,没有生成has_
方法
更新消息
更新规则
更新消息有三个场景:新增、修改、删除
新增:
新增字段的名称和已存在字段冲突(命名和编号)
修改:
-
禁止修改已存在字段的编号
-
int32
、uint32
、int64
、uint64
和bool
是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。如果解析的数值和对应类型不匹配,会采用与C++一致的处理方案(比如说原本是int64
,之后改为了int32
,反序列化的时候,会直接截断)兼容性:
向前兼容:老模块可以识别新模块的协议,这些会被当中未知字段
向后兼容:新模块可以识别老模块的协议
-
sint32 和 sint64 相互兼容但不与其他的整型兼容。
-
string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。
-
bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
-
fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容。
-
enum 与 int32,uint32, int64 和 uint64 兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语言采⽤不同的处理方案:例如,未识别的 proto3 枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。
-
oneof:
将⼀个单独的值更改为新 oneof 类型成员之⼀是安全和⼆进制兼容的。
若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可行的。
将任何字段移⼊已存在的 oneof 类型是不安全的
删除:
-
不能直接删除已存在的字段
演示:
演示代码就是基于上面的,就不贴全部了,在gitee仓库里可以看见.
这里是一致的,此时client和service的协议都是一致的
//语法指定行 syntax = "proto3"; //定义声明空间 package s_contacts; message Address { string home_address = 1; //家庭地址 string unit_address = 2; //单位地址 } //定义联系人message message PeopleInfo { //标识字段编号 string name = 1; int32 age = 2; message Phone { string number = 1; } repeated Phone phone = 3; } //通讯录message message Contacts { repeated PeopleInfo contacts = 1; }
修改
service
的contacts.proto
//语法指定行 syntax = "proto3"; //定义声明空间 package s_contacts; message Address { string home_address = 1; //家庭地址 string unit_address = 2; //单位地址 } //定义联系人message message PeopleInfo { //标识字段编号 string name = 1; //int32 age = 2; int32 birthday = 2; message Phone { string number = 1; } repeated Phone phone = 3; } //通讯录message message Contacts { repeated PeopleInfo contacts = 1; }
这里的
client
读取反序列化之后,将生日字段设置到了年龄字段上,这是因为client
的proto
文件编号为2的字段是age
,而service
的proto
文件编号为2的字段,改为了birthday
,protobuf
编码会将值设置到使用相同编号的字段上。所以要移除老字段,要保证不再使用相同或者注释的字段编号。
要保证不使用删除或者被注释的字段编号,可以采用reserved
关键字,用来指定一批编号,作为保留项,如果之后使用这里面的编号,会直接报警
也可以使用to
表示一个范围:
reserved 100 to 200;
也可以保留字段名称:
reserved "age";
这里新设置的“王五”信息,反序列化的时候就是正常的了,而“李四”的信息,还是错误的,这是因为新增“李四”的时候,生日编号还是为2,序列化到文件当中了。
未知字段
上面新增联系人的时候,有生日信息,而反序列化输出的时候,并没有输出关于联系人的生日信息。这是因为client
的proto
文件当中,没有添加生日字段。不过这个信息并不会删除,而是将这个信息存放在未知字段当中。
原本
proto3
在解析消息时,总是会自动丢弃未知字段。而在3.5版本当中,重新引入了未知字段的保留机制,会包含在序列化的结果当中。
图片来源:《protobuf》基础语法3_proto repeated跟reserved-CSDN博客
输出未知字段消息
inline int number() const; //返回未知字段的编号
inline Type type() const; //返回枚举类型
#include<iostream>
#include<fstream>
#include"contacts.pb.h"
using namespace std;
using namespace google::protobuf;
void PrintContacts(const c_contacts::Contacts& contacts)
{
for(int i = 0; i < contacts.contacts_size(); i++)
{
cout << "--------联系人" << i+1 << "--------" << endl;
const c_contacts::PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
for(int j = 0; j < people.phone_size(); j++)
{
const c_contacts::PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number() << endl;
}
const Reflection* reflection = c_contacts::PeopleInfo::GetReflection();
const UnknownFieldSet& set = reflection->GetUnknownFields(people);
for(int j = 0; j < set.field_count(); j++)
{
const UnknownField& unknown_field = set.field(j);
cout << "未知字段" << j+1 << ": " << " 编号: " << unknown_field.number();
switch (unknown_field.type())
{
case UnknownField::Type::TYPE_VARINT:
cout << " 值: " << unknown_field.varint() << endl;
break;
case UnknownField::Type::TYPE_LENGTH_DELIMITED:
cout << " 值: " << unknown_field.length_delimited() << endl;
break;
//case...
default:
break;
}
}
}
}
int main()
{
c_contacts::Contacts contacts;
//读取本地存在通讯录文件
fstream input("../contacts.bin", ios::in | ios::binary);
if(!contacts.ParseFromIstream(&input))
{
cerr << "parse error" << endl;
input.close();
return -1;
}
//输出通讯录
PrintContacts(contacts);
}
option选项
option
选项会影响编译器的一些处理方式,选择的完整列表在google/protobuf/descriptor.proto
中定义
常用选项
optimize_for
:文件选项,可以设置三个优化级别
-
SPEED
:生成的代码是高度优化的,代码运行效率高,但是生成的代码编译之后会占用更多空间,这是默认选项 -
``CODE_SIZE`:生成最少的类,会占用更少的空间,运行效率较低
-
LIFE_RUNTIME
:生成的代码执行效率⾼,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供 encoding+序列化功能, 所以我们在链接 BP 库时仅需链接libprotobuf-lite,⽽非libprotobuf。这种模式通常用于资源有限的平台,例如移动手机平台中 -
allow_alias
: 允许将相同的常量值分配给不同的枚举常量,用来定义别名。该选项为枚举选项。enum PhoneType { option allow_alias = true; MP = 0; TEL = 1; LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错 }