自学内容网 自学内容网

Protobuf——基本使用

什么是Protobuf

Protobuf是 Google的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法,它可⽤于(数据)通信协议、数据存储等。
Protobuf类比于XML,是一种灵活,高效,自动化机制的结构数据序列化方法,但是比XML更小,更快,更灵活。(可以自己选择定义不同的数据结构)

为什么需要Protobuf

序列化和反序列化

  • 序列化:把对象转换为字节序列的过程
  • 反序列化:将字节序列的恢复为对象的过程。

什么情况下需要序列化

  • 存储数据:当你想把内存的对象状态保存到一个文件中或者存储到一个数据中。
  • 网络传输: 网络传输中,都是以二进制来传输的,我们无法直接传递对象,所以都需要先序列化。

Protobuf的特点

  • 语言无关、平台无关: protobuf支持java、c++、python等语言,支持多平台
  • 高效:比XML更小、更快、更简单
  • 扩展性好、兼容性好:可以更新数据结构,不破坏原有的旧程序。(例如兼容老版本)

Protobuf的使用特点

在这里插入图片描述

  • 编写.proto文件
  • 然后编译出.pb.h .pb.cc文件,调用其中的接口就可以完成序列化和反序列化了

proto3语法
Protocol Buffers 语⾔版本3,简称proto3,是.proto文件的最新版本。
它简化了protocol Buffers语言,它允许你使用C++、Java等多种语言来生成protocol buffer的代码。

package声明符
它其实很简单,就是命名空间,用来防止我们定义发生冲突

定义消息
为什么要定义消息呢?

  • 要知道,在网络传输中,我们需要使用成熟的协议或者自定义协议来处理我们的消息,所以protobuf就是以message的方式来支持我们可以自己来定制协议字段的。
syntax = "proto3"; //指定proto文件版本
package contacts;//package声明符
message 消息类型名{

}

有了以上的认识后,下面我会以一个通讯录的例子来讲解一下protobuf的使用

首先为contacts.proto定义联系人的字段

syntax = "proto3";

package contacts;

// 定义联系⼈消息 

message PeopleInfo {
//这是消息字段格式 字段类型 字段名 = 字段唯⼀编号
string name = 1; 
int32 age = 2;
}

但是注意一下,字段的唯一编号的范围:

  • 1-536,870,911(2^19-1),其中1900-19999不可用

1900-19999不可用是因为:在protobuf中,对这些字段进行了预留。

编译contacts.proto文件

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc 是 Protocol Buffer 提供的命令⾏编译⼯具。

--proto_path 指定 被编译的.proto⽂件所在⽬录,可多次指定。可简写成 -I 
IMPORT_PATH 。如不指

 定该参数,则在当前⽬录进⾏搜索。当某个.proto ⽂件 import 其他 
.proto ⽂件时,或需要编译的 .proto ⽂件不在当前⽬录下,这时就要⽤-I来指定搜索⽬录。

--cpp_out= 指编译后的⽂件为 C++ ⽂件。

OUT_DIR 编译后⽣成⽂件的⽬标路径。

path/to/file.proto 要编译的.proto⽂件。

所以,我们直接编译protoc --cpp_out=. contacts.proto,生成.pb.*文件了。

其中.pb.h是存放类的声明的,而.pb.cc是存放类的实现的。 下面是一部分代码。

class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
 public:
 using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
 void CopyFrom(const PeopleInfo& from);
 using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
 void MergeFrom( const PeopleInfo& from) {

PeopleInfo::MergeImpl(*this, from);
 }
 static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
 return "PeopleInfo";
 }
 // string name = 1;

 void clear_name();
 const std::string& name() const;
 template <typename ArgT0 = const std::string&, typename... ArgT>
 void set_name(ArgT0&& arg0, ArgT... args);
 std::string* mutable_name();
 PROTOBUF_NODISCARD std::string* release_name();
 void set_allocated_name(std::string* name);
 // int32 age = 2;
 void clear_age();
 int32_t age() const;
 void set_age(int32_t value);
};

class MessageLite {
public:
 //序列化: 
 bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件流 
 bool SerializeToArray(void *data, int size) const;
 bool SerializeToString(string* output) const;
 
 //反序列化: 
 bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化动作 
 bool ParseFromArray(const void* data, int size);
bool ParseFromString(const string& data);
};

以上的序列化方法没有本质的区别,只是序列化后输出的格式不同,可以根据不同的使用场景来使用。但是要注意一点:序列化的结果是二进制的,不是文本格式。

Protobuf数据类型

  • 枚举类型: 在proto文件中,第一个常量值必须是0,同时proto也会将第一个枚举常量作为enum字段的默认值
enum{
XX = 0;
YY = 1;
}

在这里插入图片描述

  • repeaded类型
    消息中可以包含字段任意多次(包含0次), 可以理解为一个数组
syntax = "proto3";
package contacts;


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

// 联系人
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; // 电话
}
  • oneof类型
    如果消息中有很多可选字段,只有一个使用,就可以用oneof。也能有节约内存的效果

```bash
syntax = "proto3";
package contacts;


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

// 联系人
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; // 电话

    oneof other_contact { // 其他联系⽅式:多选⼀
        string qq = 4;
        string weixin = 5;
    }
}
  • map类型
    语法支持创建一个关联映射字段,格式如下

map<key_type, value_type> map_field = N;

  • key_type 是除了float和bytes类型以外的任意标量类型。value_type 可以是任意类型。
  • map字段不可以⽤repeated修饰
  • map中存⼊的元素是⽆序的
syntax = "proto3";
package contacts;


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

// 联系人
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; // 电话

    oneof other_contact { // 其他联系方式:多选
        string qq = 4;
        string weixin = 5;
    }

    map<string,string> remark = 6; //备注
}

所以,通讯录最终版本如下

contact.proto文件

syntax = "proto3";

package contacts2;

import "google/protobuf/any.proto";

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

message PeopleInfo{
    string name = 1;
    int32 age = 2;
    message Phone{
        string phone = 1;
        enum PhoneType{
            MP = 0;// 移动电话
            TEL = 1; //固定电话
        }
        PhoneType type = 2;
    }
    repeated Phone phone = 3;
    google.protobuf.Any data = 4;

    //可选字段中的字段编号,不能与可选字段的编号冲突。
    //不能在oneof中使用repeated字段。
    //如果在oneof中设置了多个,那么只会保留最后一次设置的成员,之前设置的oneof成员会自动清除
    oneof other_contact{
        string qq = 5;
        string wechat = 6;
    }

    map<string,string> remark = 7;

}

message Contacts{
    repeated PeopleInfo contacts = 1;
}
read.cc

#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;

void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); i++)
    {
        cout << "---------------联系人" << i + 1 << "---------------" << endl;
        const contacts2::PeopleInfo &people = contacts.contacts(i);
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;

        for (int j = 0; j < people.phone_size(); j++)
        {
            const contacts2::PeopleInfo_Phone &phone = people.phone(j);
            cout << "联系人电话" << j + 1 << ":" << phone.phone();

            cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }

        // 使⽤ Is() ⽅法可以⽤来判断存放的消息类型是否为 typename T。
        if (people.has_data() && people.data().Is<contacts2::Address>())
        {
            contacts2::Address address;
            //使⽤ UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型。
            //将people中的any类型取出来转换为其他类型 并放入address中
            people.data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "联系人家庭地址:" << address.home_address() << endl;
            }
            else if (!address.unit_address().empty())
            {
                cout << "联系人单位地址:" << address.unit_address() << endl;
            }
        }

        //other_contact_case 可以获取到当前设置了的字段
        switch(people.other_contact_case()){
            case contacts2::PeopleInfo::OtherContactCase::kQq:
            cout << "联系人qq号" << people.qq() <<endl;
             case contacts2::PeopleInfo::OtherContactCase::kWechat:
             cout << "联系人微信号" << people.wechat() <<endl;
            case contacts2::PeopleInfo::OtherContactCase::OTHER_CONTACT_NOT_SET:
            break;
        }

        if(people.remark_size())
        {
            cout << "备注信息: " << endl;
        }
        for(auto it = people.remark().cbegin(); it != people.remark().cend(); it++)
        {
           cout << " " << it->first << ": " << it->second << endl;
        }



    }
}

int main()
{
    contacts2::Contacts contacts;

    fstream input("contacts.bin", ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

    PrintContacts(contacts);

    return 0;
}

在这里插入图片描述


原文地址:https://blog.csdn.net/m0_72165281/article/details/143221989

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!