Protobuf 编码规则及c++使用详解

Protobuf 编码规则及c++使用详解

Protobuf 介绍

Protocol Buffers (a.k.a., protobuf) are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data
Protocol Buffers(简称为protobuf)是谷歌的语言无关、平台无关、可扩展的机制,用于序列化结构化数据。

protobuf的主要目的是在不同应用程序之间高效地传输和存储数据。与XML和JSON等文本格式相比,protobuf的编码格式更紧凑,解析速度更快,占用的存储空间更小。此外,protobuf还支持版本兼容性,使得在数据结构发生变化时能够向后兼容。

在音视频领域,信令和打点数据量非常大,采用JSON等文本方式,虽然可读性较大,但数据提交太大,不利于传输和节省带宽,因此二进制编码方式(protobuf)非常适合这个领域。

c++ 使用详解

1、定义.proto文件 (message.proto)

syntax = "proto3";

package tutorial;

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;

  enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_MOBILE = 1;
    PHONE_TYPE_HOME = 2;
    PHONE_TYPE_WORK = 3;
  }

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

  repeated PhoneNumber phones = 4;
}

包含了常用的整型,字符串,枚举,结构体,repeated(数组)类型

2、使用protoc编译器生成c++源码文件(message.pb.h, message.pb.cc)

protoc --cpp_out=. message.proto
libprotobuf和protoc编译器如何安装?
在ubuntu系统下:

# sudo apt update
# sudo apt install libprotobuf-dev protobuf-compiler

3、编写测试文件(main.cpp)

// g++ -std=c++11 main.cpp message.pb.cc -lprotobuf -o main

#include "message.pb.h"
#include 
#include 
#include 
#include 

struct CppPerson {
    std::string name = "John";
    int32_t id = 1;
    std::string email = "[email protected]";

    enum CppPhoneType {
        UNSPECIFIED = 0,
        MOBILE = 1,
        HOME = 2,
        WORK = 3,
    };
  
    struct CppPhoneNumber {
        std::string number;
        CppPhoneType type;
    };

    std::vector<CppPhoneNumber> phones;
};

int main() {
    // Create a CppPerson object
    CppPerson cpp_person; 
    cpp_person.phones.push_back({"123456789", CppPerson::HOME});
    cpp_person.phones.push_back({"987654321", CppPerson::WORK});

    // 创建一个 Person 对象
    tutorial::Person person;
    person.set_name(cpp_person.name);
    person.set_id(cpp_person.id);
    person.set_email(cpp_person.email);
    for (auto &s : cpp_person.phones) {
        auto phone = person.add_phones();
        phone->set_number(s.number);
        phone->set_type(static_cast<tutorial::Person::PhoneType>(s.type));
    }

    // 将 Person 对象序列化为字节流
    std::string serialized;
    person.SerializeToString(&serialized);
    std::cout << "serialized size: " << serialized.size() << std::endl;

    // message <--> json
    std::string json;
    google::protobuf::util::JsonOptions json_options;
    //  json_options.add_whitespace = true;
    google::protobuf::util::MessageToJsonString(person, &json, json_options);
    std::cout << "JSON: " << json << std::endl;
    std::cout << "JSON size: " << json.size() << std::endl;
    tutorial::Person person_from_json;
    google::protobuf::util::JsonStringToMessage(json, &person_from_json);
    std::string person_from_json_serialized;
    person_from_json.SerializeToString(&person_from_json_serialized);
    std::cout << "Person from JSON serialized size: " << person_from_json_serialized.size() << std::endl;

    // 将字节流写入文件
    std::fstream output("person.bin", std::ios::out | std::ios::binary);
    output.write(serialized.c_str(), serialized.size());
    output.close();

    // 从文件中读取字节流
    std::fstream input("person.bin", std::ios::in | std::ios::binary);
    std::string serialized_input((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());

    // 将字节流反序列化为 Person 对象
    tutorial::Person person2;
    person2.ParseFromString(serialized_input);

    // 输出 Person 对象的信息
    std::cout << "Person:" << std::endl;
    std::cout << "  name: " << person2.name() << std::endl;
    std::cout << "  id: " << person2.id() << std::endl;
    std::cout << "  email: " << person2.email() << std::endl;
    for (int i = 0; i < person2.phones_size(); i++) {
        auto &phone_number = person2.phones(i);
        std::cout << "  phone number: " << phone_number.number() << std::endl;
        std::cout << "  phone type: " << phone_number.type() << std::endl;
    }

    return 0;
}

4、编译运行

# g++ -std=c++11 main.cpp message.pb.cc -lprotobuf -o main

# ./main

serialized size: 52
JSON: {"name":"John","id":1,"email":"[email protected]","phones":[{"number":"123456789","type":"PHONE_TYPE_HOME"},{"number":"987654321","type":"PHONE_TYPE_WORK"}]}
JSON size: 152
Person from JSON serialized size: 52
Person:
  name: John
  id: 1
  email: [email protected]
  phone number: 123456789
  phone type: 2
  phone number: 987654321
  phone type: 3

查看编码好的二进制(person.bin)
Protobuf 编码规则及c++使用详解_第1张图片

5、编码后二进制流分析

Protobuf 编码规则及c++使用详解_第2张图片

编码规则

第一个字节包含了字段编号(Filed Number)和wired type, 组合起来代表message里一个字段的Key信息
bit 7

0-代表该字节自解释完整
1-代表后续还有字节,需要整合后续字节提取信息 (当字段数超过15个是采用这种方式,即由多个字节编码key)

<这个bit就是varint的关键功能位,variant中每个字节的最高位bit称之为most significant bit(MSB),如果该bit为0意味着这个字节为表示当前整数的最后一个字节,如果为1则表示后面还有至少1个字节,可见,varint的终止位置其实是自解释的。>

bit 6-bit 3

存储字段编号,field number (fn)

bit 2-bit 0

存储wired type (wt)

常用的类型主要有VARINT和LEN

  • wire type=0、1、5,编码为key+数据,只有一个数据,可能占数个字节,数据在编码时自带终止标记;
  • wire type=2,编码为key+length+数据,length指示了数据长度,可能有多个数据,顺序排序

Protobuf 编码规则及c++使用详解_第3张图片
Protobuf 编码规则及c++使用详解_第4张图片

总结:二进制流存储方式为key data key data key data…
比如上述例子:
Protobuf 编码规则及c++使用详解_第5张图片

6、番外:protobuf和JSON互转

如上述示例,转换为的json为:

JSON: {"name":"John","id":1,"email":"[email protected]","phones":[{"number":"123456789","type":"PHONE_TYPE_HOME"},{"number":"987654321","type":"PHONE_TYPE_WORK"}]}
JSON size: 152

可见JSON紧压缩方式大小为152Byte,而PB压缩大小为52Byte,非常省空间。

Protobuf,它只需要简单地将一个二进制序列,按照指定的格式读取到C++对应的结构类型中就可以了。消息的decoding过程也可以通过几个位移操作组成的表达式计算即可完成。而对于字符串、自定义对象类型的数据,Protobuf在存储的时候,也存储了该数据的字节长度,读取起来也非常快。

参考

https://protobuf.dev/overview/

你可能感兴趣的:(linux,c++,音视频,c++,protobuf,variant)