一. ProtoBuf 简介

Protobuf 是 Google 开发的一种轻便、高效的结构化数据存储/交换格式,类似于 XML 或 JSON,但比它们更小、更快、更简单。Protobuf 定义了一种接口描述语言 (IDL),用于描述数据结构,然后可以自动生成各种编程语言的代码来操作这些数据结构。

1. 用途

  • 数据序列化:用于网络通信、数据存储等场景,将数据结构序列化为字节流。
  • 跨语言服务:由于 Protobuf 支持多种编程语⾔,它非常适合构建跨语言的 RPC (远程过程调用) 服务。
  • 数据兼容性:Protobuf 通过版本控制机制,可以向后兼容旧的数据格式。

2. 安装

sudo apt install protobuf-compiler libprotobuf-dev

# 如果需要使用grpc相关编译
sudo apt install protobuf-compiler-grpc

由于在环境搭建中已经安装了 ProtoBuf,这里不需要再次安装了。

3. 头文件和链接库

在这里插入图片描述

Protobuf 没有提供这种总头文件,需要的是通过 .proto 文件编译生成的那个 .h 文件!

程序编译时库的链接

-lprotobuf

4. 基本组成

  • 消息:Protobuf 中的基本数据结构单元,类似于 C++ 中的结构体或 Java 中的类。
  • 字段:消息中的数据项,每个字段都有一个唯一的标签号和数据类型。
  • 枚举:用于定义一组命名的常数。
  • 服务:在 RPC 场景中定义服务接口。

5. 语法

Protobuf 使用自己的 IDL 定义数据结构,以下是 protobuf 的一个简单示例:

// person.proto
syntax = "proto3"; // 描述语法版本
package person; // 声明包名称(C++对应的是命名空间)

// 默认情况 protoc 命令并不会针对 service 服务生成对应 rpc 代码,需要开启选项才会进行生成
option cc_generic_services = true;

// 定义枚举类型
enum SexType {
    unknow = 0;
    man = 1;
    woman = 2;
}

// 定义消息类型
message Student {
    int32 sn = 1;
    string name = 2;
    SexType sex = 3;
    repeated float score = 4;
    map<int32, string> other = 5;
}

message PersonRequest {
    int32 sn = 1;
}

message PersonResponse {
    Student student = 1;
}

// 定义一个 RPC 服务
service PersonService {
    rpc getPerson (PersonRequest) returns (PersonResponse) {}
}

6. 编译

在这里插入图片描述

7. 说明

生成的 person.pb.h 文件

// 命名空间和 proto 中的 package 对应
namespace person {
    // 性别枚举
    enum SexType : int {
        unknow = 0,
        man = 1,
        woman = 2,
    };
    // PROTOBUF_NAMESPACE_ID 这里等价于 ::google::protobuf
    class Student PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {
    public:
        void CopyFrom(const Student& from); // 从另一个对象复制数据
        void MergeFrom(const Student& from); // 从另一个对象合并数据
    public:
        // 枚举字段编号
        enum : int {
            kScoreFieldNumber = 4,
            kOtherFieldNumber = 5,
            kNameFieldNumber = 2,
            kSnFieldNumber = 1,
            kSexFieldNumber = 3,
        };
        // repeated float score = 4;
        int score_size() const; // 获取元素数量
        void clear_score(); // 清空元素
        float score(int index) const; // 获取元素
        void set_score(int index, float value); // 设置元素
        void add_score(float value); // 添加元素
        const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& score() const; // 获取元素列表
        ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* mutable_score(); // 获取可修改的元素列表

        // map<int32, string> other = 5;
        int other_size() const; // 获取元素数量
        void clear_other(); // 清空元素
        const ::PROTOBUF_NAMESPACE_ID::Map< ::PROTOBUF_NAMESPACE_ID::int32, std::string >& other() const; // 获取元素列表
        ::PROTOBUF_NAMESPACE_ID::Map< ::PROTOBUF_NAMESPACE_ID::int32, std::string >* mutable_other(); // 获取可修改的元素列表

        // string name = 2;
        void clear_name();
        const std::string& name() const; // 获取元素指针
        void set_name(const std::string& value); // 设置元素值
        void set_name(std::string&& value); // 设置元素值
        void set_name(const char* value); // 设置元素值
        void set_name(const char* value, size_t size); // 设置元素值
        std::string* mutable_name(); // 获取可修改的元素指针
        std::string* release_name(); // 释放元素指针
        void set_allocated_name(std::string* name); // 设置可分配的元素指针
        GOOGLE_PROTOBUF_RUNTIME_DEPRECATED("The unsafe_arena_ accessors for"
        "    string fields are deprecated and will be removed in a"
        "    future release.") // 警告:不安全的Arena访问器已被弃用
        std::string* unsafe_arena_release_name(); // 不安全地释放元素指针
        GOOGLE_PROTOBUF_RUNTIME_DEPRECATED("The unsafe_arena_ accessors for"
        "    string fields are deprecated and will be removed in a"
        "    future release.") // 警告:不安全的Arena访问器已被弃用
        void unsafe_arena_set_allocated_name(std::string* name); // 不安全地设置可分配的元素指针

        // int32 sn = 1;
        void clear_sn(); // 清空元素
        ::PROTOBUF_NAMESPACE_ID::int32 sn() const; // 获取元素指针
        void set_sn(::PROTOBUF_NAMESPACE_ID::int32 value); // 设置元素值

        // .person.SexType sex = 3;
        void clear_sex(); // 清空元素
        ::person::SexType sex() const; // 获取元素指针
        void set_sex(::person::SexType value); // 设置元素值
    };
    
    class PersonRequest PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {
    public:
        static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor(); // 获取描述符
        void CopyFrom(const PersonRequest& from); // 从另一个对象复制数据
        void MergeFrom(const PersonRequest& from); // 从另一个对象合并数据
    public:
        // 枚举字段编号
        enum : int {
            kSnFieldNumber = 1,
        };
        // int32 sn = 1;
        void clear_sn(); // 清空元素
        ::PROTOBUF_NAMESPACE_ID::int32 sn() const; // 获取元素指针
        void set_sn(::PROTOBUF_NAMESPACE_ID::int32 value); // 设置元素值
    };

    class PersonResponse PROTOBUF_FINAL : public ::PROTOBUF_NAMESPACE_ID::Message {
    public:
        static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor(); // 获取描述符
        void CopyFrom(const PersonResponse& from); // 从另一个对象复制数据
        void MergeFrom(const PersonResponse& from); // 从另一个对象合并数据
    public:
        // 枚举字段编号
        enum : int {
            kStudentFieldNumber = 1,
        };
        // .person.Student student = 1;
        bool has_student() const; // 是否有元素
        void clear_student(); // 清空元素
        const ::person::Student& student() const; // 获取元素指针
        ::person::Student* release_student(); // 释放元素指针
        ::person::Student* mutable_student(); // 获取可修改的元素指针
        void set_allocated_student(::person::Student* student); // 设置可分配的元素指针
        GOOGLE_PROTOBUF_RUNTIME_DEPRECATED("The unsafe_arena_ accessors for"
        "    message fields are deprecated and will be removed in a"
        "    future release.") // 警告:不安全的Arena访问器已被弃用
        void unsafe_arena_set_allocated_student(::person::Student* student); // 不安全地设置可分配的元素指针
        GOOGLE_PROTOBUF_RUNTIME_DEPRECATED("The unsafe_arena_ accessors for"
        "    message fields are deprecated and will be removed in a"
        "    future release.") // 警告:不安全的Arena访问器已被弃用
        ::person::Student* unsafe_arena_release_student(); // 不安全地释放元素指针
    };

    class PersonService : public ::PROTOBUF_NAMESPACE_ID::Service { // 服务类
    public:
        static const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* descriptor(); // 获取描述符
        virtual void getPerson(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                            const ::person::PersonRequest* request,
                            ::person::PersonResponse* response,
                            ::google::protobuf::Closure* done); // 异步调用getPerson方法

        const ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor* GetDescriptor(); // 获取描述符
        virtual void CallMethod(const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method,
                            ::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                            const ::PROTOBUF_NAMESPACE_ID::Message* request,
                            ::PROTOBUF_NAMESPACE_ID::Message* response,
                            ::google::protobuf::Closure* done); // 调用方法
        const ::PROTOBUF_NAMESPACE_ID::Message& GetRequestPrototype(
                            const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const; // 获取请求原型
        const ::PROTOBUF_NAMESPACE_ID::Message& GetResponsePrototype(
                            const ::PROTOBUF_NAMESPACE_ID::MethodDescriptor* method) const; // 获取响应原型 
    };

    class PersonService_Stub : public PersonService { // 服务类的桩类
    public:
        void getPerson(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::person::PersonRequest* request,
                       ::person::PersonResponse* response,
                       ::google::protobuf::Closure* done); // 异步调用getPerson方法
    };
}

二. ProtoBuf 接口

1. Message

在 protobuf 中定义的每个 message 结构,在编译后都会生成一个继承于 Message 类的派生类,其内部包含了消息中各个字段的操作接口,但是除了这些操作接口外,还需要了解一下这些消息结构所生成的派生类中继承而来的其他操作:

namespace google {
    namespace protobuf {
        class PROTOBUF_EXPORT Message : public MessageLite { // 消息类
        public:
            std::string GetTypeName() const override; // 获取消息类型名
            void Clear() override; // 清除消息数据
            const Descriptor* GetDescriptor(); // 获取描述符
            const Reflection* GetReflection(); // 获取反射对象
        };
        class PROTOBUF_EXPORT MessageLite { // 消息基类
        public:
            bool ParseFromString(const std::string& data); // 解析字符串
            bool ParseFromArray(const void* data, int size); // 解析数组
            bool SerializeToString(std::string* output) const; // 序列化字符串
            bool SerializeToArray(void* data, int size) const; // 序列化数组
            std::string SerializeAsString() const; // 序列化字符串
        };
    }
}

2. Map

protobuf 中 message 消息结构中支持 map 字段的定义,其字段类型对应的是 protobuf 中的 Map 类 (注意并非 C++STL 中的 map,虽然他们的基本操作都差不多,但是类型是不同的),为了方便对 map 字段的操作,因此在这里也看一看 Map 类中所定义的常用接口。

namespace google {
    namespace protobuf {
        template <typename Key, typename T> 
        struct MapPair {
            MapPair(const Key& other_first, const T& other_second)
        };
        
        template <typename Key, typename T>
        class Map {
        public:
            using iterator = typename std::map<Key, T>::iterator;
            using const_iterator = typename std::map<Key, T>::const_iterator;
            iterator begin();
            iterator end();
            void clear();
            size_type size() const;
            bool empty() const;
            iterator find(const TrivialKey& k);
            std::pair<iterator, bool> insert(const KeyValuePair& kv);
            iterator operator[](const TrivialKey& k);
            void erase(iterator it);
        };
    }
}

3. Util

protobuf 中的工具操作有很多,当前仅贴出了用于 protobuf 和 json 之间的转换接口。

namespace google {
    namespace protobuf {
        namespace util {
            namespace error {
                enum Code { // 错误码枚举类
                    OK = 0, // 成功
                    CANCELLED = 1, // 取消
                    UNKNOWN = 2, // 未知错误
                    INVALID_ARGUMENT = 3, // 无效参数
                    DEADLINE_EXCEEDED = 4, // 超时
                    NOT_FOUND = 5, // 未找到
                    ALREADY_EXISTS = 6, // 已存在
                    PERMISSION_DENIED = 7, // 权限拒绝
                    UNAUTHENTICATED = 16, // 未认证
                    RESOURCE_EXHAUSTED = 8, // 资源耗尽
                    FAILED_PRECONDITION = 9, // 前置条件失败
                    ABORTED = 10, // 中止
                    OUT_OF_RANGE = 11, // 超出范围
                    UNIMPLEMENTED = 12, // 未实现
                    INTERNAL = 13, // 内部错误
                    UNAVAILABLE = 14, // 不可用
                    DATA_LOSS = 15, // 数据丢失
                };
            }

            class PROTOBUF_EXPORT Status { // 状态类,用于表示操作是否成功
            public:
                bool ok(); // 是否成功
                StringPiece message(); // 错误信息
                string ToString(); // 返回状态的字符串表示
            };
                
            struct JsonParseOptions { // JSON解析选项结构体
                bool ignore_unknown_fields; // 是否忽略未知字段
                bool case_insensitive_enum_parsing; // 是否不区分枚举类型的大小写
            };

            // 从json格式字符转换proto消息接口
            util::Status JsonStringToMessage(StringPiece input, Message* message, const JsonParseOptions& options);
            // 从json格式字符转换proto消息接口,默认选项
            util::Status JsonStringToMessage(StringPiece input, Message* message)
            
            struct JsonPrintOptions { // JSON打印选项结构体
                bool add_whitespace; // 是否添加空白字符让json更易读
                bool always_print_primitive_fields; // 是否总是输出基本类型的字段
                bool always_print_enums_as_ints; // 是否总是将枚举类型作为整数打印
                bool preserve_proto_field_names; // 是否保留 pb 字段名称
            };
            typedef JsonPrintOptions JsonOptions; // JSON打印选项别名

            // 从proto消息转换为json格式字符串接口
            util::Status MessageToJsonString(const Message& message, std::string* output, const JsonOptions& options);
            // 从proto消息转换为json格式字符串接口,默认选项
            util::Status MessageToJsonString(const Message& message, std::string* output);
        }
    }
}

三. ProtoBuf 使用样例

1.1 目录结构

protobuf/
|-- formatConversion.cc
|-- makefile
|-- person.pb.cc
|-- person.pb.h
|-- person.proto
|-- serializeAndUnserialize.cc

1.2 项目构建

all: serializeAndUnserialize formatConversion
serializeAndUnserialize: serializeAndUnserialize.cc person.pb.cc
	g++ -o $@ $^ -std=c++17 -lprotobuf
formatConversion: formatConversion.cc person.pb.cc
	g++ -o $@ $^ -std=c++17 -lprotobuf

.PHONY: clean
clean:
	rm -f serializeAndUnserialize formatConversion

1.3 代码编写

1.3.1 序列化与反序列化
// serializeAndUnserialize.cc
#include "person.pb.h"

std::string serialize_test() {
    // 定义一个学生对象
    person::Student stu;
    stu.set_sn(123456);
    stu.set_name("zhangsan");
    stu.set_sex(person::SexType::man);
    stu.add_score(95.5);
    auto score = stu.mutable_score();
    score->Add(90.5);
    score->Add(85.5);
    auto other = stu.mutable_other();
    (*other)[1] = "first";
    other->insert({2, "second"});
    // 序列化对象
    auto str = stu.SerializeAsString();
    return str;
}

void unserialize_test(const std::string& str) {
    // 定义一个学生对象
    person::Student stu;
    // 反序列化对象
    bool ret = stu.ParseFromString(str);
    if (ret == false) {
        std::cout << "unserialize failed" << std::endl;
        return;
    }
    std::cout << stu.sn() << std::endl;
    std::cout << stu.name() << std::endl;
    std::cout << stu.sex() << std::endl;
    for (auto s : stu.score()) {
        std::cout << s << " ";
    }
    std::cout << std::endl;
    for (auto it = stu.other().begin(); it != stu.other().end(); ++it) {
        std::cout << it->first << ": " << it->second << " ";
    }
    std::cout << std::endl;
}

int main() 
{
    auto str = serialize_test();
    unserialize_test(str);

    return 0;
}

在这里插入图片描述

1.3.2 protobuf 与 jsoncpp 格式转换
// formatConversion.cc
#include <google/protobuf/util/json_util.h>
#include "person.pb.h"

void json2pb_test() {
    // 定义一个json字符串
    std::string json_str = R"({
        "sn": 123456,
        "name": "zhangsan",
        "sex": "man",
        "score": [95.5, 90.5, 85.5],
        "other": {
            "1": "first",
            "2": "second"
        }
    })";
    // 定义一个学生对象
    person::Student stu;
    // 从json字符串反序列化到学生对象
    auto ret = google::protobuf::util::JsonStringToMessage(json_str, &stu);
    if (ret.ok() == false) {
        std::cout << "json2pb failed" << ret.ToString() << std::endl;
        return;
    }
    // 打印反序列化后的对象
    std::cout << stu.sn() << std::endl;
    std::cout << stu.name() << std::endl;
    std::cout << stu.sex() << std::endl;
    for (auto s : stu.score()) {
        std::cout << s << " ";
    }
    std::cout << std::endl;
    for (auto it = stu.other().begin(); it != stu.other().end(); ++it) {
        std::cout << it->first << ": " << it->second << " ";
    }
    std::cout << std::endl;
}

void pb2json_test() {
    // 定义一个学生对象
    person::Student stu;
    stu.set_sn(123456);
    stu.set_name("zhangsan");
    stu.set_sex(person::SexType::man);
    stu.add_score(95.5);
    auto score = stu.mutable_score();
    score->Add(90.5);
    score->Add(85.5);
    auto other = stu.mutable_other();
    (*other)[1] = "first";
    other->insert({2, "second"});
    // 序列化对象到json字符串
    std::string json_str;
    google::protobuf::util::JsonPrintOptions options;
    options.add_whitespace = true; // 添加空格,使json字符串更易读
    auto ret = google::protobuf::util::MessageToJsonString(stu, &json_str, options);
    if (ret.ok() == false) {
        std::cout << "pb2json failed" << ret.ToString() << std::endl;
        return;
    }
    std::cout << json_str << std::endl;
}
 
int main() 
{
    json2pb_test();
    std::cout << std::endl;
    pb2json_test();

    return 0;
}

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐