当前位置:   article > 正文

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

protobuf编码规则

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

包含了常用的整型,字符串,枚举,结构体,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
  • 1
  • 2

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

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

#include "message.pb.h"
#include <google/protobuf/util/json_util.h>
#include <iostream>
#include <fstream>
#include <initializer_list>

struct CppPerson {
    std::string name = "John";
    int32_t id = 1;
    std::string email = "john@163.com";

    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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

4、编译运行

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

# ./main

serialized size: 52
JSON: {"name":"John","id":1,"email":"john@163.com","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: john@163.com
  phone number: 123456789
  phone type: 2
  phone number: 987654321
  phone type: 3

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

查看编码好的二进制(person.bin)
在这里插入图片描述

5、编码后二进制流分析

在这里插入图片描述

编码规则

第一个字节包含了字段编号(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指示了数据长度,可能有多个数据,顺序排序

在这里插入图片描述
在这里插入图片描述

总结:二进制流存储方式为key data key data key data…
比如上述例子:
在这里插入图片描述

6、番外:protobuf和JSON互转

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

JSON: {"name":"John","id":1,"email":"john@163.com","phones":[{"number":"123456789","type":"PHONE_TYPE_HOME"},{"number":"987654321","type":"PHONE_TYPE_WORK"}]}
JSON size: 152
  • 1
  • 2

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

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

参考

https://protobuf.dev/overview/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/430279
推荐阅读
相关标签
  

闽ICP备14008679号