赞
踩
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)非常适合这个领域。
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(数组)类型
protoc --cpp_out=. message.proto
libprotobuf和protoc编译器如何安装?
在ubuntu系统下:
# sudo apt update
# sudo apt install libprotobuf-dev protobuf-compiler
// 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; }
# 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
查看编码好的二进制(person.bin)
第一个字节包含了字段编号(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…
比如上述例子:
如上述示例,转换为的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
可见JSON紧压缩方式大小为152Byte,而PB压缩大小为52Byte,非常省空间。
Protobuf,它只需要简单地将一个二进制序列,按照指定的格式读取到C++对应的结构类型中就可以了。消息的decoding过程也可以通过几个位移操作组成的表达式计算即可完成。而对于字符串、自定义对象类型的数据,Protobuf在存储的时候,也存储了该数据的字节长度,读取起来也非常快。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。