proto3语法

news2024/10/5 14:51:01

文章目录

    • 字段规则
    • 消息类型定义与使用
    • 序列化写入文件
    • 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;
}

分文件:

image-20241002114649405

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();
}

image-20241002152451170

hexdump工具

上面的程序生成了二进制文件,采用hexdump工具,可以将二进制文件进行转换。

-C表示生成规范的16进制和ASCII码

hexdump -C contacts.bin

image-20241002154428687

反序列化读取文件

取出元素:

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);

}

image-20241002155649591

decode命令选项

查看所有命名选项:

protoc -h

image-20241002160115648

--decode选项可以将二进制消息转换成文本格式

protoc --decode=contacts.Contacts contacts.proto < contacts.bin
  • --decode=contacts.Contacts表明解析的是contacts命名空间下的Contacts
  • < contacts.bin表明标准输入重定向到contacts.bin文件

image-20241002160740315

如果是utf-8汉字,则转换成八进制格式

enum类型

  • 零值常量必须存在,且必须作为枚举类型第一个枚举值
  • 枚举常量值必须在32位整数范围内(负值不生效)
  • 同级枚举类型下,如果有相同常量名称,会引发冲突
syntax = "proto3";

enum PhoneType
{
    //常量名称建议采用大小
    MP = 0;
    TEL = 1;
}

嵌套定义:

syntax = "proto3";

message Phone
{
   enum PhoneType
    {
        //常量名称建议采用大小
        MP = 0;
        TEL = 1;
    } 
}

如果有个.proto文件枚举名称冲突,如果声明了package命名空间,则不影响

image-20241002162538107

image-20241002162524092

更新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;
}

重新编译:

image-20241002163500651

设置电话类型

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;  
        }
    }
}

image-20241002164354323

这里的张三和lisa之前并没有设置电话类型,会默认设置枚举值常量为零的常量

Any类型

Any类型是protobuf事先定义好的一个message

Any类型是一个泛型类型,可以存储任意的消息类型

image-20241002165324663

更新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;
}

image-20241002170028154

提供的一些方法:

image-20241002170221926

设置地址信息

新增地址信息其实是往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;
            }
        }
    }
}

image-20241003193455003

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字段设置多次,只会保留最后一次设置的值

image-20241004183537024

oneof字段设置的多个类型,会转换成枚举类型:

image-20241004183915538

设置其他联系人信息

更新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;
        }
    }
}

image-20241004185332586

map类型

map就是一个映射字段map<key_type, value_type> map_field = N;

  • key_type除了floatbytes类型以外任意标量类型;value_type可以是任意类型
  • 不能用repeated修饰
  • 存入的元素是无需的

contacts.proto添加:

map<string, string> remark = 7; //备注

image-20241004191333509

添加备注信息

更新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;
        }
    }
}

image-20241004192751614

默认值

反序列化时,如果反序列化的二进制序列不包含某个字段,则会采用默认值填充:

  • 字符串:默认空字符串

  • 字节:默认空字节

  • 布尔值:默认false

  • 数值类型:默认0

  • 枚举:默认第一个枚举值,必须为0

  • 消息字段:取决于依赖的语言

  • 设置了repeated字段:默认为空

  • 对于消息字段、oneof字段、any字段,C++和Java都有has_方法检测该字段是否被设置

    对于标量数据类型,在proto3语法当中,没有生成has_方法

更新消息

更新规则

更新消息有三个场景:新增、修改、删除

新增:

新增字段的名称和已存在字段冲突(命名和编号)

修改:

  • 禁止修改已存在字段的编号

  • int32uint32int64uint64bool是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。如果解析的数值和对应类型不匹配,会采用与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;
    }
    

    image-20241004201318088

    修改servicecontacts.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;
    }
    

    image-20241005105645668

    这里的client读取反序列化之后,将生日字段设置到了年龄字段上,这是因为clientproto文件编号为2的字段是age,而serviceproto文件编号为2的字段,改为了birthdayprotobuf编码会将值设置到使用相同编号的字段上。

    所以要移除老字段,要保证不再使用相同或者注释的字段编号。

要保证不使用删除或者被注释的字段编号,可以采用reserved关键字,用来指定一批编号,作为保留项,如果之后使用这里面的编号,会直接报警

image-20241005110810564

也可以使用to表示一个范围:

reserved 100 to 200;

也可以保留字段名称:

reserved "age";

image-20241005111124771

这里新设置的“王五”信息,反序列化的时候就是正常的了,而“李四”的信息,还是错误的,这是因为新增“李四”的时候,生日编号还是为2,序列化到文件当中了。

未知字段

上面新增联系人的时候,有生日信息,而反序列化输出的时候,并没有输出关于联系人的生日信息。这是因为clientproto文件当中,没有添加生日字段。不过这个信息并不会删除,而是将这个信息存放在未知字段当中。

原本proto3在解析消息时,总是会自动丢弃未知字段。

而在3.5版本当中,重新引入了未知字段的保留机制,会包含在序列化的结果当中。

《protobuf》基础语法3_protobuf reserved-CSDN博客

图片来源:《protobuf》基础语法3_proto repeated跟reserved-CSDN博客

输出未知字段消息

inline int number() const;	//返回未知字段的编号
inline Type type() const;	//返回枚举类型

image-20241005121021722

#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);

}

image-20241005121519502

option选项

image-20241005122621083

option选项会影响编译器的一些处理方式,选择的完整列表在google/protobuf/descriptor.proto中定义

image-20241005123535130

常用选项

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; 这⼀⾏会编译报错
    }
    

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

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

相关文章

Leetcode 剑指 Offer II 097.不同的子序列

题目难度: 困难 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个字符串 s 和一个字符串 t &#xff0c;计算在 s 的子序列…

NVIDIA网卡系列之ConnectX-5规格信息(100G-PCIe 3.0x16-8PF512VF-2016年发布)

背景 NVIDIA ConnectX-5系列的网卡&#xff0c;早期还在Mellanox未被NVIDIA收购的时候就发布了&#xff0c;主流支持100G&#xff0c;主要用在PCIe3.0&#xff0c;最大支持200G的产品。虽然已经发布多年&#xff0c;但是目前还是在大量使用。100Gbps的速率对比普通网卡来讲&am…

基于SSM的电影院售票系统设计与实现

文未可获取一份本项目的java源码和数据库参考。 前言 近些年的电影在人们文娱活动中占据重要地位&#xff0c;另外&#xff0c;由于人们的生活越来越富有&#xff0c;越来越多的人们不再选择在家里看电影&#xff0c;而是选择去电影院看电影。但是&#xff0c;以往的售票方式是…

在Visual Studio中使用CMakeLists.txt集成EasyX库的详细指南

EasyX库是一款专为Windows平台设计的轻量级C图形库&#xff0c;适合初学者和教育领域使用。结合Visual Studio和CMake工具链&#xff0c;用户可以轻松创建C项目&#xff0c;并集成EasyX库&#xff0c;实现丰富的图形编程效果。本文将详细介绍如何在Visual Studio中通过CMakeLis…

分布式事务(Seata-AT模式)

角色说明 TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。 RM (Resource Manager) - 资源管理器 管理分…

macOS编译和运行prometheus2.54

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文详述了在macOS(M2芯片)上编译和运行prometheus2.54版本的过程&#xff0c;以及安装node_exporter和grafana并使用prometheus指标进行展示 本地…

【含文档】基于Springboot+Vue的活力健身馆管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

HTB:Preignition[WriteUP]

连接至HTB服务器并启动靶机 靶机IP&#xff1a;10.129.157.49 分配IP&#xff1a;10.10.16.12 1.Directory Brute-forcing is a technique used to check a lot of paths on a web server to find hidden pages. Which is another name for this? (i) Local File Inclusion, (…

喜欢听音乐的朋友,怎么能少这个音乐插件?

喜欢听音乐的朋友有福利了&#xff0c;今天小编给大家带来一个非常酷的音乐插件&#xff0c;让你的音乐在桌面跳动起来~&#xff01;想象一下&#xff0c;随着旋律的起伏&#xff0c;你的桌面仿佛被赋予了生命&#xff0c;音符跳跃、光影交错&#xff0c;每一首歌都化作了眼前流…

电源入口防护电路

电源入口防护电路 原则:先防护&#xff0c;后防反&#xff0c;最后滤波基本防护器件防反措施 原则:先防护&#xff0c;后防反&#xff0c;最后滤波 在设计电路保护方案时&#xff0c;遵循“先防护&#xff0c;后防反&#xff0c;最后滤波”的原则是非常重要的。这种顺序确保了…

Python入门--函数

目录 1. 函数介绍 2. 函数的定义 3. 函数的参数 4. 函数的返回值 5. 函数说明文档 6. 函数的嵌套调用 7. 函数的作用域 (1). 局部变量 (2). 全局变量 (3). global关键字 1. 函数介绍 函数&#xff1a;是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能…

【无题】夜入伊人笑愉,泪湿心夜难眠。

在这句诗中&#xff0c;意境描绘了一种深沉的情感体验&#xff0c;充满了温柔与哀愁。诗人通过“夜入伊人笑愉”开启了一段梦境之旅&#xff0c;其中“夜入”象征着进入梦境的状态。在这个梦幻的世界里&#xff0c;诗人与心爱的人欢笑嬉戏&#xff0c;那份快乐和亲昵如同真实的…

java高并发场景RabbitMQ的使用

场景是面试时被问到&#xff0c;一次性请求100多万个前端请求&#xff0c;请问你如果进行后端处理。因为之前的电商也没有一次性这么大的业务量&#xff0c;所以只是前端nginx做了对应的负载均衡技术。所以回答的不是那么流畅。面试官的回答你可以用RabbitMQ做分流,削峰,异步处…

什么是 ARP 欺骗和缓存中毒攻击?

如果您熟悉蒙面歌王&#xff0c;您就会明白蒙面歌王的概念&#xff1a;有人伪装成别人。然后&#xff0c;当面具掉下来时&#xff0c;您会大吃一惊&#xff0c;知道了这位名人是谁。类似的事情也发生在 ARP 欺骗攻击中&#xff0c;只是令人惊讶的是&#xff0c;威胁行为者利用他…

中小型网络系统综合实验

一、实验要求 1.pc端自动获取ip地址&#xff0c;可以互通访问&#xff0c;可以访问域名解析服务器 2.设置vlan&#xff0c;三层交换机实现不同vlan之间的交流 3.设置静态路由&#xff0c;配置nat地址转换&#xff0c;实现全网可达 二、实验思路 1.首先给LSW2配置vlan 10 &a…

双十一不能错过的好物推荐!强推五款超好用的品牌好物

双十一快到了&#xff0c;这个时候的优惠力度都是最大的&#xff0c;还不知道买啥的小伙伴们赶紧来看这篇好物推荐&#xff01;以下五款产品是我花了几天时间精心挑选出来的&#xff0c;看完之后保证你想加入购物车&#xff01; 品牌好物推荐一、希亦CG超声波清洗机 如果你带眼…

用Manim实现高尔顿板(Galton Board)

高尔顿板的介绍 高尔顿板&#xff08;Galton Board&#xff09;&#xff0c;有时也称为贝尔图&#xff08;Bean Machine&#xff09;&#xff0c;是由英国统计学家弗朗西斯高尔顿&#xff08;Francis Galton&#xff09;于19世纪末发明的一种物理装置&#xff0c;用于演示随机分…

【智能算法应用】蒲公英优化算法求解二维路径规划问题

摘要 在二维路径规划问题中&#xff0c;通常需要在不规则的障碍物环境中找到一条从起点到终点的最优路径。本文应用蒲公英优化算法&#xff08;DOA&#xff09;进行路径规划&#xff0c;其能够有效避开障碍物并找到最短路径。通过实验验证&#xff0c;DOA具有收敛速度快、全局…

2024年【金属非金属矿山(露天矿山)安全管理人员】模拟试题及金属非金属矿山(露天矿山)安全管理人员模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员模拟试题参考答案及金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员考试试题解析是安全生产模拟考试一点通题库老师及金属非金属矿山&#…

SAP学习笔记 - Basis01 - 创建Client ,拷贝Client

最近工作当中用到了Client间数据移送的内容&#xff0c;想把自己的虚机给弄两个Client。 最后也没完全弄成&#xff0c;先把过程整理一下&#xff0c;以后有空接着弄。 目录 1&#xff0c;SALE - 新建逻辑系统 2&#xff0c;SCC4 - 分配Client到集团 3&#xff0c;RZ10 - 取…