Protobuf数据类型

news2024/11/25 1:03:01

enum类型

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:

枚举类型名称:
使⽤驼峰命名法,⾸字⺟⼤写。例如: MyEnum

常量值名称:
全⼤写字⺟,多个字⺟之间⽤连接。例如: ENUM_CONST = 0;

比如我们定义一个PhoneType类型

enum PhoneType{
    MP= 0;  //移动电话
    TEL= 1; //固定电话
}

要注意枚举类型的定义有以下⼏种规则:

  • 0值常量必须存在,且要作为第⼀个元素。这是为了与proto2的语义兼容:第⼀个元素作为默认值,且值为0。
  • 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
  • 枚举的常量值在32位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)。

定义规则

将两个‘具有相同枚举值名称’的枚举类型放在单个.proto⽂件下测试时,编译后会报错:某某某常
量已经被定义!所以这⾥要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个.proto⽂件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个.proto⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明package,每个proto⽂件中的枚举类型都在最外层,算同级。
  • 多个.proto⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了package,不算同级。

情况一:同级出现常量重名的情况

syntax="proto3";
package enum1;

enum PhoneType1 {
    MP=0;
    TEL=1;
}

enum PhoneType2 {
    MP=0;
}

image-20230602002838802

情况二:嵌套

enum PhoneType1 {
    MP=0;
    TEL=1;
}

message phone {
    string number=1;
    enum PhoneType2 {
        MP=0;
    }
}

image-20230602003107599

情况三:多文件未使用package

image-20230602003359719

编译,发现报错。错误信息为重复定义。

image-20230602003452513

情况四:多文件使用package

image-20230602003931203

改进通讯录

更新通讯录。为电话号码加上类型。

修改contacts.proto文件

enum PhoneType {
    MP=0;   //移动电话
    TEL=1;  //固定电话
}

message Phone {
    string number=1;
    PhoneType type=2;
}

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated Phone phone=3;
}

message Contacts{
    repeated PeopleInfo people=1;
}

更新write.cc文件:添加电话的类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{
    for (int j = 0;; j++)
    {
        std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;
        std::cout << " 请输入联系人姓名: ";
        string name;
        getline(std::cin, name);
        if(name=="退出"){
            break;
        }
        contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息
        peopleinfo->set_name(name);
        std::cout << " 请输入联系人年龄: ";
        string age;
        getline(std::cin, age);
        peopleinfo->set_age(age);
        for (int i = 0;; i++)
        {
            std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";
            string number;
            getline(std::cin, number);
            if (number.empty())
            {
                break;
            }
            contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息
            phone->set_number(number);
            std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";
            int type=0;
            std::cin>>type;
            std::cin.ignore(256,'\n');
            switch (type)
            {
            case 0:
                phone->set_type(contacts1::MP);
                break;
            case 1:
                phone->set_type(contacts1::TEL);
                break;
            default:
                cout << "⾮法选择,使⽤默认值!" << endl;
                break;
            }
        }
    }
}

执行结果:

image-20230602010001278

更新read.cc文件

void PrintContacts(contacts1::Contacts& contacts){
    for(int i=0;i<contacts.people_size();i++){
        contacts1::PeopleInfo peopleinfo=contacts.people(i);    //通过下标获取people
        std:cout<<"----------联系人"<<i+1<<"----------"<<std::endl;
        std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;
        std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;
        int j=1;
        for(;j<=peopleinfo.phone_size();j++){
            std::cout<<"电话: "<<peopleinfo.phone(j-1).number();
            contacts1::Phone phone=peopleinfo.phone(j-1);
            std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;

        }
    }
}

输出结果:

image-20230602010810937

当我们未设置值时,默认第一个值为默认值。

Any类型

字段还可以声明为Any类型,可以理解为泛型类型。使⽤时可以在Any中存储任意消息类型。Any类型的字段也⽤repeated来修饰。(可以理解为Go语言中的interface)

Any类型是google已经帮我们定义好的类型,在安装ProtoBuf时,其中的include⽬录下查找所有google已经定义好的.proto⽂件。

image-20230604171158173

改进通讯录

修改contacts.proto文件,添加Any类型。

syntax="proto3";
package contacts1;
import "google/protobuf/any.proto";     //引入any.proto文件

message Address{
    string home_address=1;  //家庭地址
    string unit_address=2;  //单位地址
}

enum PhoneType {
    MP=0;   //移动电话
    TEL=1;  //固定电话
}

message Phone {
    string number=1;
    PhoneType type=2;
}

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated Phone phone=3;
    google.protobuf.Any address=4;
}

message Contacts{
    repeated PeopleInfo people=1;
}

image-20230604172808177

修改write.cc:将message Address转化为Any类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{
    for (int j = 0;; j++)
    {
        std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;
        std::cout << " 请输入联系人姓名: ";
        string name;
        getline(std::cin, name);
        if(name=="退出"){
            break;
        }
        contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息
        peopleinfo->set_name(name);
        std::cout << " 请输入联系人年龄: ";
        string age;
        getline(std::cin, age);
        peopleinfo->set_age(age);
        for (int i = 0;; i++)
        {
            std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";
            string number;
            getline(std::cin, number);
            if (number.empty())
            {
                break;
            }
            contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息
            phone->set_number(number);
            std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";
            int type=0;
            std::cin>>type;
            std::cin.ignore(256,'\n');
            switch (type)
            {
            case 0:
                phone->set_type(contacts1::MP);
                break;
            case 1:
                phone->set_type(contacts1::TEL);
                break;
            default:
                std::cout << "⾮法选择,使⽤默认值!" << std::endl;
                break;
            }
        }
        contacts1::Address address;
        std::cout<<" 请输入家庭地址:";
        string home_address;
        getline(std::cin,home_address);
        address.set_home_address(home_address);
        std::cout<<" 请输入单位地址:";
        string unit_address;
        getline(std::cin,unit_address);
        address.set_unit_address(unit_address);

        google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。
        //类型转化:转化为Any类型。Any类型也就是通用类型
        data->PackFrom(address);
    }
}

image-20230604181134592

修改read.cc:将Any类型转化为message address类型

void PrintContacts(const contacts1::Contacts& contacts){
    for(int i=0;i<contacts.people_size();i++){
        const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取people
        std::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;
        std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;
        std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;
        int j=1;
        for(;j<=peopleinfo.phone_size();j++){
            std::cout<<"电话: "<<peopleinfo.phone(j-1).number();
            contacts1::Phone phone=peopleinfo.phone(j-1);
            std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;

        }
        //如果有address成员,并且address成员的类型是Address
        if(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){
            contacts1::Address address;
            peopleinfo.address().UnpackTo(&address);
            //如果家庭地址不为空
            if(!address.home_address().empty()){
                std::cout<<" 家庭地址:"<<address.home_address()<<std::endl;
            }
		   //如果单位地址不为空
            if(!address.unit_address().empty()){
                std::cout<<" 单位地址:"<<address.unit_address()<<std::endl;
            }
        }
    }

image-20230604183314361

oneof类型

如果消息中有很多可选字段,并且将来同时只有⼀个字段会被设置,那么就可以使⽤oneof 加强这个⾏为,也能有节约内存的效果。

考虑以下的场景:一个人的性别只能说男或者女。只能是两种性别中的一个,既不能都选也不能都不选。

  • 每一个oneof类型都被转化为了一个enum类型
message Address{
    string home_address=1;  //家庭地址
    string unit_address=2;  //单位地址
}

enum PhoneType {
    MP=0;   //移动电话
    TEL=1;  //固定电话
}

message Phone {
    string number=1;
    PhoneType type=2;
}

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated Phone phone=3;
    google.protobuf.Any address=4;
    oneof gender {
        string male=5;
        string female=6;
    }
}
message Contacts{
    repeated PeopleInfo people=1;
}

image-20230604185429434

可以看到oneof类型都被转化为了enum类型,其成员就是oneof包含的值加上NOT_SET。

注意:

  • 可选字段中的字段编号,不能与⾮可选字段的编号冲突。
  • 不能在oneof中使⽤repeated字段。
  • 将来在设置oneof字段中值时,如果将oneof中的字段设置多个,那么只会保留最后⼀次设置的成员,之前设置的oneof成员会⾃动清除。

改进通讯录

修改write.cc文件:添加oneof类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{
    for (int j = 0;; j++)
    {
        std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;
        std::cout << " 请输入联系人姓名: ";
        string name;
        getline(std::cin, name);
        if(name=="退出"){
            break;
        }
        contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息
        peopleinfo->set_name(name);
        std::cout << " 请输入联系人年龄: ";
        string age;
        getline(std::cin, age);
        peopleinfo->set_age(age);
        for (int i = 0;; i++)
        {
            std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";
            string number;
            getline(std::cin, number);
            if (number.empty())
            {
                break;
            }
            contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息
            phone->set_number(number);
            std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";
            int type=0;
            std::cin>>type;
            std::cin.ignore(256,'\n');
            switch (type)
            {
            case 0:
                phone->set_type(contacts1::MP);
                break;
            case 1:
                phone->set_type(contacts1::TEL);
                break;
            default:
                std::cout << "⾮法选择,使⽤默认值!" << std::endl;
                break;
            }
        }
        contacts1::Address address;
        std::cout<<" 请输入家庭地址:";
        string home_address;
        getline(std::cin,home_address);
        address.set_home_address(home_address);
        std::cout<<" 请输入单位地址:";
        string unit_address;
        getline(std::cin,unit_address);
        address.set_unit_address(unit_address);

        google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。
        //类型转化:转化为Any类型
        data->PackFrom(address);

        //设置oneof类型
        std::cout<<" 请输入联系人性别:(1.male  2.female):";
        int male=0;
        std::cin>>male;
        std::cin.ignore(256,'\n');
        if(male==1){
            string male="male";
            peopleinfo->set_male(male);
        }
        else if(male==2){
            string female="female";
            peopleinfo->set_female(female);
        }
        else{
            std::cout<<" 非法选择,设置为默认值!"<<std::endl;
        }
        
    }
}

image-20230604212013379

修改read.cc文件:读取oneof类型

void PrintContacts(const contacts1::Contacts& contacts){
    for(int i=0;i<contacts.people_size();i++){
        const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取people
        std::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;
        std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;
        std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;
        int j=1;
        for(;j<=peopleinfo.phone_size();j++){
            std::cout<<"电话: "<<peopleinfo.phone(j-1).number();
            contacts1::Phone phone=peopleinfo.phone(j-1);
            std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;

        }
        //如果有address成员,并且address成员的类型是Address
        if(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){
            contacts1::Address address;
            peopleinfo.address().UnpackTo(&address);
            if(!address.home_address().empty()){
                std::cout<<"家庭地址:"<<address.home_address()<<std::endl;
            }

            if(!address.unit_address().empty()){
                std::cout<<"单位地址:"<<address.unit_address()<<std::endl;
            }
        }

        //读取oneof类型
        
        /* 
            第一种写法
        if (people.has_qq()) {
        
        } else if (people.has_weixin()) {
        
        } */

        //写法二
        switch (peopleinfo.gender_case())
        {
        case contacts1::PeopleInfo::kMale:
            std::cout<<"性别:male"<<std::endl;
            break;
        case contacts1::PeopleInfo::kFemale:
            std::cout<<"性别:female"<<std::endl;
            break;
        case contacts1::PeopleInfo::GENDER_NOT_SET:
            std::cout<<"性别:未设置"<<std::endl;
            break;
        default:
            std::cout<<"未知错误! "<<std::endl;
            break;
        }
    }
}

image-20230604213445055

map类型

语法⽀持创建⼀个关联映射字段,也就是可以使⽤map类型去声明字段类型,格式为:

map<key_type, value_type> map_field = N;

要注意的是:

  • key_type是除了float和bytes类型以外的任意标量类型。 value_type 可以是任意类型。
  • map字段不可以⽤repeated修饰
  • map中存⼊的元素是⽆序的
message Address{
    string home_address=1;  //家庭地址
    string unit_address=2;  //单位地址
}

enum PhoneType {
    MP=0;   //移动电话
    TEL=1;  //固定电话
}

message Phone {
    string number=1;
    PhoneType type=2;
}

//定义联系人消息
message PeopleInfo{
    string name=1;
    string age=2;
    repeated Phone phone=3;
    google.protobuf.Any address=4;
    oneof gender {
        string male=5;
        string female=6;
    }
    map<string,string> remarks=7;
}
message Contacts{
    repeated PeopleInfo people=1;
}

image-20230604222227122

上述的代码中,对于Map类型的字段:

  • 清空map:clear_⽅法
  • 设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。**设置⽅法为mutable⽅法,返回值为Map类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。 **

改进通讯录

修改write.cc文件:添加备注项

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{
    for (int j = 0;; j++)
    {
        std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;
        std::cout << " 请输入联系人姓名: ";
        string name;
        getline(std::cin, name);
        if(name=="退出"){
            break;
        }
        contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息
        peopleinfo->set_name(name);
        std::cout << " 请输入联系人年龄: ";
        string age;
        getline(std::cin, age);
        peopleinfo->set_age(age);
        for (int i = 0;; i++)
        {
            std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";
            string number;
            getline(std::cin, number);
            if (number.empty())
            {
                break;
            }
            contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息
            phone->set_number(number);
            std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";
            int type=0;
            std::cin>>type;
            std::cin.ignore(256,'\n');
            switch (type)
            {
            case 0:
                phone->set_type(contacts1::MP);
                break;
            case 1:
                phone->set_type(contacts1::TEL);
                break;
            default:
                std::cout << "⾮法选择,使⽤默认值!" << std::endl;
                break;
            }
        }


        //增加Any类型
        contacts1::Address address;
        std::cout<<" 请输入家庭地址:";
        string home_address;
        getline(std::cin,home_address);
        address.set_home_address(home_address);
        std::cout<<" 请输入单位地址:";
        string unit_address;
        getline(std::cin,unit_address);
        address.set_unit_address(unit_address);
        google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。
        //类型转化:转化为Any类型
        data->PackFrom(address);


        //增加oneof类型
        std::cout<<" 请输入联系人性别:(1.male  2.female):";
        int male=0;
        std::cin>>male;
        std::cin.ignore(256,'\n');
        if(male==1){
            string male="male";
            peopleinfo->set_male(male);
        }
        else if(male==2){
            string female="female";
            peopleinfo->set_female(female);
        }
        else{
            std::cout<<" 非法选择,设置为默认值!"<<std::endl;
        }
        

        //增加map类型
        for(int i=1;;i++){
            std::cout<<"添加第"<<i<<"条备注的标题:(只输入回车表示添加结束)";
            string remark_key;
            getline(std::cin,remark_key);
            if(remark_key.empty()){
                break;
            }
            std::cout<<"添加第"<<i<<"条备注的内容:";
            string remark_value;
            getline(std::cin,remark_value);
            
            //开辟remarks空间
            google::protobuf::Map<std::string, std::string> *remarks=peopleinfo->mutable_remarks();
            remarks->insert({remark_key,remark_value});
        }
    }
}

image-20230604224359840

修改read.cc文件

void PrintContacts(const contacts1::Contacts& contacts){
    for(int i=0;i<contacts.people_size();i++){
        const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取people
        std::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;
        std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;
        std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;
        int j=1;
        for(;j<=peopleinfo.phone_size();j++){
            std::cout<<"电话: "<<peopleinfo.phone(j-1).number();
            contacts1::Phone phone=peopleinfo.phone(j-1);
            std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;

        }
        //如果有address成员,并且address成员的类型是Address
        if(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){
            contacts1::Address address;
            peopleinfo.address().UnpackTo(&address);
            if(!address.home_address().empty()){
                std::cout<<"家庭地址:"<<address.home_address()<<std::endl;
            }

            if(!address.unit_address().empty()){
                std::cout<<"单位地址:"<<address.unit_address()<<std::endl;
            }
        }

        /* 
            第一种写法
        if (people.has_qq()) {
        
        } else if (people.has_weixin()) {
        
        } */

        //写法二
        switch (peopleinfo.gender_case())
        {
        case contacts1::PeopleInfo::kMale:
            std::cout<<"性别:male"<<std::endl;
            break;
        case contacts1::PeopleInfo::kFemale:
            std::cout<<"性别:female"<<std::endl;
            break;
        case contacts1::PeopleInfo::GENDER_NOT_SET:
            std::cout<<"性别:未设置"<<std::endl;
            break;
        default:
            std::cout<<"未知错误! "<<std::endl;
            break;
        }

        //打印备注
        if(peopleinfo.remarks_size()){
            for(auto it=peopleinfo.remarks().cbegin();it!=peopleinfo.remarks().end();it++){
                std::cout << " " << it->first << ": " << it->second << std::endl;
            }
    }
}

image-20230605163047688

默认值

反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为false。
  • 对于数值类型,默认值为0。
  • 对于枚举,默认值是第⼀个定义的枚举值,必须为0。
  • 对于消息字段,未设置该字段。它的取值是依赖于语⾔。
  • 对于设置了repeated的字段的默认值是空的(通常是相应语⾔的⼀个空列表)。
  • 对于 消息字段 、 oneof字段 和 any字段 ,C++和Java语⾔中都有has_⽅法来检测当前字段
    是否被设置。
  • 对于标量数据类型,在proto3语法下,没有生成has_方法。

更新消息

当我们需要删除或者修改一些字段时,如果是直接进行修改就会出现错误。因此需要使用reserved关键字。

验证直接删除的影响

image-20230605182324203

我们将文件分别在client和server目录下保留一份。然后修改server目录下contacts.proto文件的内容:

image-20230605183230688

分别执行read程序和server程序观察结果

执行write程序

image-20230605185149213

执行read程序

image-20230605185227711

直接进行修改,就会出现数据丢失的现象。这和protobuf的规则有关:protobuf通过字段序号进行确认。

reserved保留项字段

更新规则

如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可 :

  • 禁⽌修改任何已有字段的字段编号。
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段
  • int32,uint32,int64,uint64和bool是完全兼容的。可以从这些类型中的⼀个改为另⼀个,⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与C++⼀致的处理⽅案(例如,若将64位整数当做32位进⾏读取,它将被截断为32位)。
  • sint32和sint64相互兼容但不与其他的整型兼容。
  • string和bytes在合法UTF-8字节前提下也是兼容的。
  • bytes包含消息编码版本的情况下,嵌套消息与bytes也是兼容的。
  • fixed32与sfixed32兼容,fixed64与sfixed64兼容。
  • enum与int32,uint32,int64和uint64兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语⾔采⽤不同的处理⽅案:例如,未识别的proto3枚举类型会被保存在消息中,但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。
  • oneof:
    ◦ 将⼀个单独的值更改为新oneof类型成员之⼀是安全和⼆进制兼容的。
    ◦ 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新oneof类型也是可⾏的。
    ◦ 将任何字段移⼊已存在的oneof类型是不安全的。
message Message {
	// 设置保留项
	reserved 100, 101, 200 to 299;
	reserved "field3", "field4";
	// 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
	// reserved 102, "field5";
	// 设置保留项之后,下⾯代码会告警
	int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
	int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
	int32 field3 = 102; //告警:Field name 'field3' is reserved
	int32 field4 = 103; //告警:Field name 'field4' is reserved
}

使用reserved关键字,修改server的消息

image-20230605202210086

执行write程序

image-20230605202419205

执行read程序

image-20230605202618408

未知字段

在上面的通讯录程序中,我们向service⽬录下的contacts.proto新增了‘birthday’字段,但对于client相关的代码并没有任何改动。验证后发现新代码序列化的消息(service)也可以被旧代码(client)解析。并且这⾥要说的是,新增的‘⽣⽇’字段在旧程序(client)中其实并没有丢失,⽽是会作为旧程序的未知字段。

  • 未知字段:解析结构良好的protocol buffer已序列化数据中的未识别字段的表⽰⽅式。例如,当旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。
  • 本来,proto3在解析消息时总是会丢弃未知字段,但在3.5版本中重新引⼊了对未知字段的保留机制。**所以在3.5或更⾼版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中。 **

打印未知字段

修改client.cc文件的内容

void PrintContacts(const r_contacts::Contacts &contacts)
{
    for (int i = 0; i < contacts.people_size(); i++)
    {
        const r_contacts::PeopleInfo &peopleinfo = contacts.people(i); // 通过下标获取people
        std::cout << "----------联系人" << i + 1 << "----------" << std::endl;
        std::cout << "联系人姓名: " << peopleinfo.name() << std::endl;
        std::cout << "联系人年龄: " << peopleinfo.age() << std::endl;
        int j = 1;
        for (; j <= peopleinfo.phone_size(); j++)
        {
            std::cout << "电话: " << peopleinfo.phone(j - 1).number();
            r_contacts::Phone phone = peopleinfo.phone(j - 1);
            std::cout << "  类型[" << phone.type() << "]" << std::endl;
        }
        // 如果有address成员,并且address成员的类型是Address
        if (peopleinfo.has_address() && peopleinfo.address().Is<r_contacts::Address>())
        {
            r_contacts::Address address;
            peopleinfo.address().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                std::cout << "家庭地址:" << address.home_address() << std::endl;
            }

            if (!address.unit_address().empty())
            {
                std::cout << "单位地址:" << address.unit_address() << std::endl;
            }
        }

        /*
            第一种写法
        if (people.has_qq()) {

        } else if (people.has_weixin()) {

        } */

        // 写法二
        switch (peopleinfo.gender_case())
        {
        case r_contacts::PeopleInfo::kMale:
            std::cout << "性别:male" << std::endl;
            break;
        case r_contacts::PeopleInfo::kFemale:
            std::cout << "性别:female" << std::endl;
            break;
        case r_contacts::PeopleInfo::GENDER_NOT_SET:
            std::cout << "性别:未设置" << std::endl;
            break;
        default:
            std::cout << "未知错误! " << std::endl;
            break;
        }

        // 打印备注
        if (peopleinfo.remarks_size())
        {
            for (auto it = peopleinfo.remarks().cbegin(); it != peopleinfo.remarks().end(); it++)
            {
                std::cout << " " << it->first << ": " << it->second << std::endl;
            }
        }


        /*
            打印未知字段
        */
        const google::protobuf::Reflection *reflect = r_contacts::PeopleInfo::GetReflection(); // 获取未知字段
        const google::protobuf::UnknownFieldSet &unknowset = reflect->GetUnknownFields(peopleinfo);
        for (int j = 0; j < unknowset.field_count(); j++)
        {
            const google::protobuf::UnknownField &unknow_field = unknowset.field(j);
            cout << "未知字段" << j + 1 << ":"
                 << " 字段编号: " << unknow_field.number()
                 << " 类型: " << unknow_field.type();
            switch (unknow_field.type())
            {
            case google::protobuf::UnknownField::Type::TYPE_VARINT:
                cout << " 值: " << unknow_field.varint() << endl;
                break;
            case google::protobuf::UnknownField::Type::TYPE_LENGTH_DELIMITED:
                cout << " 值: " << unknow_field.length_delimited() << endl;
                break;
            }
        }
    }
}

image-20230605204757541

protobuf主要类

image-20230605215029947

MessageLite类介绍

  • MessageLite从名字看是轻量级的message,仅仅提供序列化、反序列化功能。
  • 类定义在google提供的message_lite.h中。

**Message类介绍 **

我们⾃定义的message类,都是继承⾃Message。

  • Message最重要的两个接⼝GetDescriptor/GetReflection,可以获取该类型对应的Descriptor对象指针和Reflection对象指针。
  • 类定义在google提供的message.h中
//google::protobuf::Message 部分代码展⽰
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;

**Descriptor类介绍 **

  • Descriptor:是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto⽂件内容等。
  • 类定义在google提供的descriptor.h中
// 部分代码展⽰
class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
	string& name () const
	int field_count() const;
	const FieldDescriptor* field(int index) const;
	const FieldDescriptor* FindFieldByNumber(int number) const;
	const FieldDescriptor* FindFieldByName(const std::string& name) const;
	const FieldDescriptor* FindFieldByLowercaseName(
	const std::string& lowercase_name) const;
	const FieldDescriptor* FindFieldByCamelcaseName(
	const std::string& camelcase_name) const;
	int enum_type_count() const;
	const EnumDescriptor* enum_type(int index) const;
	const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;
	const EnumValueDescriptor* FindEnumValueByName(const std::string& name)const;
}

**Reflection类介绍 **

  • Reflection接⼝类,主要提供了动态读写消息字段的接⼝,对消息对象的⾃动读写主要通过该类完成。

  • 提供⽅法来动态访问/修改message中的字段,对每种类型,Reflection都提供了⼀个单独的接⼝⽤于读写字段对应的值。

    ◦ 针对所有不同的field类型 FieldDescriptor::TYPE_* ,需要使⽤不同的 Get*()/Set*()/Add*() 接⼝;
    ◦ repeated类型需要使⽤ GetRepeated*()/SetRepeated*() 接⼝,不可以和⾮repeated
    类型接⼝混⽤;
    ◦ message对象只可以被由它⾃⾝的 reflection(message.GetReflection()) 来操
    作;

  • 类中还包含了访问/修改未知字段的⽅法。

  • 类定义在google提供的message.h中。

UnknownFieldSet类介绍

  • UnknownFieldSet包含在分析消息时遇到但未由其类型定义的所有字段。
  • 若要将UnknownFieldSet附加到任何消息,请调⽤Reflection::GetUnknownFields()。
  • 类定义在unknown_field_set.h中。
class PROTOBUF_EXPORT UnknownFieldSet {
	inline void Clear();
	void ClearAndFreeMemory();
	inline bool empty() const;
	inline int field_count() const;
	inline const UnknownField& field(int index) const;
    inline UnknownField* mutable_field(int index);
// Adding fields ---------------------------------------------------
	void AddVarint(int number, uint64_t value);
	void AddFixed32(int number, uint32_t value);
	void AddFixed64(int number, uint64_t value);
	void AddLengthDelimited(int number, const std::string& value);
	std::string* AddLengthDelimited(int number);
	UnknownFieldSet* AddGroup(int number);
// Parsing helpers -------------------------------------------------
// These work exactly like the similarly-named methods of Message.
	bool MergeFromCodedStream(io::CodedInputStream* input);
	bool ParseFromCodedStream(io::CodedInputStream* input);
	bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);	
	bool ParseFromArray(const void* data, int size);
	inline bool ParseFromString(const std::string& data) {
	return ParseFromArray(data.data(), static_cast<int>(data.size()));
}
// Serialization.
	bool SerializeToString(std::string* output) const;
	bool SerializeToCodedStream(io::CodedOutputStream* output) const;
	static const UnknownFieldSet& default_instance();
};

**UnknownField类介绍 **

  • 表⽰未知字段集中的⼀个字段。
  • 类定义在unknown_field_set.h中
class PROTOBUF_EXPORT UnknownField {
	public:
	enum Type {
		TYPE_VARINT,
		TYPE_FIXED32,
		TYPE_FIXED64,
		TYPE_LENGTH_DELIMITED,
		TYPE_GROUP
	};
	inline int number() const;
	inline Type type() const;
    // Each method works only for UnknownFields of the corresponding type.
	inline uint64_t varint() const;
	inline uint32_t fixed32() const;
	inline uint64_t fixed64() const;
	inline const std::string& length_delimited() const;
	inline const UnknownFieldSet& group() const;
	inline void set_varint(uint64_t value);
	inline void set_fixed32(uint32_t value);
	inline void set_fixed64(uint64_t value);
	inline void set_length_delimited(const std::string& value);
	inline std::string* mutable_length_delimited();
	inline UnknownFieldSet* mutable_group();
};

option选项

.proto⽂件中可以声明许多选项,使⽤option标注。选项能影响proto编译器的某些处理⽅式。

选项分类

syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
...

由此可⻅,选项分为 ⽂件级、消息级、字段级等等,但并没有⼀种选项能作⽤于所有的类型 。

**常⽤选项列举 **

  • optimize_for:该选项为⽂件选项,可以设置protoc编译器的优化级别,分别为SPEED 、CODE_SIZE 、 LITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译.proto⽂件后⽣成的代码内容不同。
    ◦ SPEED :protoc编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间。 SPEED是默认选项。
    ◦ CODE_SIZE:proto编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中。
    ◦ LITE_RUNTIME :⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供encoding+序列化功能,所以我们在链接BP库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源有限的平台,例如移动⼿机平台中。
option optimize_for = LITE_RUNTIME;
  • allow_alias:允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。
    举个例⼦:
enum PhoneType {
	option allow_alias = true;
	MP = 0;
	TEL = 1;
	LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}	

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/613411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嵌入式烧录/刷写文件】-2.9-Intel Hex文件的地址对齐Address Alignment

案例背景(共5页精讲): 对一个Intel Hex文件&#xff0c;进行地址对齐Address Alignment。 目录 1 为什么要进行“地址对齐Address Alignment” 1.1 “对齐长度”的选择 2 使用Vector HexView工具对Hex文件进行“地址对齐Address Alignment” 2.1 “自动”完成“地址对齐Ad…

【JavaEE】Cookie与Session的前后端交互-表白墙登录设计

【JavaEE】Cookie与Session的前后端交互-表白墙登录设计 文章目录 【JavaEE】Cookie与Session的前后端交互-表白墙登录设计1. Cookie与Session1.1 后端doPost的实现1.1.1 隐藏的全局变量1.1.2 获取请求对应的HttpSession对象1.1.3 约定前后端交互方式1.1.4 验证用户名与密码是否…

你不知道的Bing聊天机器人:7个惊人的用途!

导读&#xff1a;以下是我总结的有用的方法&#xff0c;可以利用AI聊天机器人让您的生活更轻松。 本文字数&#xff1a;1600&#xff0c;阅读时长大约&#xff1a;10分钟 生成式AI工具可以用非常多的方式使你的日常生活更轻松。 AI聊天机器人在编程、写作等方面表现出色&#…

JVM入门:官网了解JVM、Java源文件运行过程、什么是类加载器、Java的类加载机制的三种方式、Tomcat的自定义类加载器

JVM入门&#xff1a;官网了解JVM、Java源文件运行过程、什么是类加载器、Java的类加载机制的三种方式、Tomcat的自定义类加载器 通过官网了解JVM官网jdk8结构图什么是JVM查看官网Java语言和虚拟机规范 Java源文件运行过程1.Java源文件经过Javac编译成字节码文件如何手动编译一个…

Java如何实现去重?这是在炫技吗?

大家好&#xff0c;我3y啊。由于去重逻辑重构了几次&#xff0c;好多股东直呼看不懂&#xff0c;于是我今天再安排一波对代码的解析吧。austin支持两种去重的类型&#xff1a;N分钟相同内容达到N次去重和一天内N次相同渠道频次去重。 Java开源项目消息推送平台&#x1f525;推送…

高性能服务器为什么需要内存池?

C/C程序为什么比起其它语言开发的程序效率要高&#xff0c;一个很重要的原因就是可以直接操作内存&#xff0c;今天就来讲讲为什么需要内存池的技术。 从一个示例开始 先看下面两段代码&#xff0c;都是去重复的创建和删除对象0x5FFFFF次&#xff0c;他们的执行后的效率怎么样…

DIP第7章知识点

文章目录 7.3 相关7.5 基图像7.6 傅里叶相关变换7.6.1 离散哈特利变换7.6.3 离散正弦变换 DIP的其他章节都好复习&#xff0c;唯独就这个第7章小波变换。复习起来十分头大&#xff0c;所以我开始写他的课后题&#xff0c;雾。 7.3 相关 已知两个连续函数 f ( x ) f(x) f(x) …

一篇博客教会你怎么使用Docker安装Redis

文章目录 1、搜索镜像2、拉取镜像3、下载配置文件4、修改配置文件5、启动容器 今天我们学习使用 Docker 安装 Redis。 1、搜索镜像 docker search redis2、拉取镜像 docker pull redis3、下载配置文件 wget http://download.redis.io/redis-stable/redis.conf下载的配置文件…

云原生Docker容器管理

docker容器相当于一个进程&#xff0c;性能接近于原生&#xff0c;几乎没有损耗&#xff1b; docker容器在单台主机上支持的数量成百上千&#xff1b; 容器与容器之间相互隔离&#xff1b; 镜像是创建容器的基础&#xff0c;可以理解镜像为一个压缩包 docker容器的管理 容器…

彩票中奖率的真相:用 JavaScript 看透彩票背后的随机算法

原本这篇文章是打算叫「假如我是彩票系统开发者」&#xff0c;但细想一下&#xff0c;如果在文章中引用太多的 JavaScript 的话&#xff0c;反而不是那么纯粹&#xff0c;毕竟也只是我的一厢情愿&#xff0c;彩票开发也不全如本文所讲&#xff0c;有所误导的话便也是得不偿失了…

理解 Java 关键字 final

原文链接 理解 Java 关键字 final final可以用来干什么 final是Java中非常常见的一个关键字&#xff0c;可以说每天都在使用它&#xff0c;虽然常见&#xff0c;但却也不见得都那么显而易见&#xff0c;今天就来研究一下final&#xff0c;以加深对它的理解和更合理的运用。 修…

gitstack使用教程

一、下载及安装 下载地址&#xff1a;https://gitstack.com/download/?spma2c4e.10696291.0.0.6d4c19a40qOauc 支持操作系统列表 本文基于2.3.12版本 下载完成后安装&#xff0c;默认安装路径为&#xff1a;c:\GitStack&#xff0c;安装过程中&#xff0c;gitstack服务会启…

医学图像分割 nnUNetV2 分割自定义2d数据集

文章目录 1 环境安装(Pytorch)1.1 安装conda1.1 安装pytorch1.3 安装nnUNet1.4 安装隐藏层&#xff08;可选&#xff09; 2 配置自定义数据集2.1 数据集格式2.2 创建需要目录2.3 数据格式转换2.3.1 修改路径与数据集名称2.3.2 修改训练集与测试集2.3.3 修改掩码所在的文件夹&am…

python基础----05-----函数的多返回值、函数的多种参数使用形式、函数作为参数传递、lambda匿名函数

一 函数的多返回值 if __name__ __main__:# 演示使用多个变量&#xff0c;接收多个返回值def test_return ():return 1,hello,Truex,y,z test_return()print(x)print(y)print(z)1helloTrue二 函数的多种参数使用形式 分为以下四种。 2.1 位置参数 位置参数调用函数时根据…

卡尔曼滤波与组合导航原理(五)序贯Kalman滤波

量测维数很高&#xff0c;而且能写成很多分量&#xff0c;每一个分量可以看成一个小量测&#xff0c;可以序贯进行量测更新 优点是&#xff1a;计算快&#xff0c;数字稳定性更好&#xff0c;我们知道矩阵求逆是和维数的三次方成正比&#xff0c;分成小矩阵求逆快&#xff08;都…

自学大语言模型之Bert和GPT的区别

Bert和GPT的区别 起源 2018 年&#xff0c;Google 首次推出 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;。该模型是在大量文本语料库上结合无监督和监督学习进行训练的。 BERT 的目标是创建一种语言模型&#xff0c;可以理解句子中…

(Day1)配置云开发提供的模板

创建云开发项目 打开微信开发者工具&#xff1b;点击“项目”->“新建项目”&#xff1b;输入项目名称和选择项目所要保存的目录&#xff1b;输入自己的AppID&#xff1b; AppID的获取&#xff0c;需要登陆微信公众平台&#xff0c;并点击“开发管理”->"开发设置…

凸优化系列——凸函数

1.凸函数的定义 凸函数直观上来说&#xff0c;就是两点之间的函数值小于两点连线的函数值 常见凸函数 线性函数既是凸函数&#xff0c;也是凹函数 对于二次函数&#xff0c;如果Q矩阵是半正定矩阵&#xff0c;那么它的二阶导为Q为半正定矩阵&#xff0c;根据凸性判定的二阶条…

SpringCloud微服务架构 --- 高级篇

一、初识Sentinel 1.1、雪崩问题及解决方案 1.1.1、雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 1.1.2、解决雪崩问题的常见方式有四种 1.1.2.1、超时处理 设定超时时间&#xff0c;请求超过一定时间…

Swagger原理

最近在基于Swagger进行二次开发&#xff0c; 来对项目的接口进行管理&#xff0c;功能实现了&#xff0c;但是不清楚swagger的工作原理&#xff0c;为了后续能更好利用Swagger来管理接口&#xff0c;而且能借鉴Swagger的原理&#xff0c;将项目中其他信息可视化展示&#xff0c…