ProtoBuf
protobuf 官网
Protocol Buffers Documentation
序列化与反序列化:Json, XML, ProtoBuf
什么是序列化和反序列化?
序列化:将数据结构或对象转换成可以存储或传输的格式(如 JSON、XML、ProtoBuf 等)的过程。
反序列化:将序列化后的数据恢复成原有的数据结构或对象的过程。
Json, XML, ProtoBuf 简介
JSON
特点:轻量级、易读、易解析的文本格式。
应用场景:常用于前后端数据交换。
序列化:直接将对象转换为 JSON 字符串。
反序列化:将 JSON 字符串解析为对象。
XML
特点:标记语言,数据自描述性强,支持复杂数据结构,但较为冗长。
应用场景:早期的 Web 服务通信,仍在某些领域中使用。
序列化:将对象转换为 XML 文档。
反序列化:将 XML 文档解析为对象。
ProtoBuf
特点:高效的二进制格式,比 XML 更小更快,跨语言跨平台,扩展性和兼容性好。
应用场景:网络通信和数据存储,尤其在性能和空间要求高的场景。
序列化和反序列化:通过编译
.proto
文件生成的代码,自动完成序列化和反序列化操作。
ProtoBuf 的概述和特点
什么是 ProtoBuf?
ProtoBuf (Protocol Buffers) 是一种由 Google 提出的用于序列化结构化数据的语言中立、平台无关的二进制格式。开发者可以用一种简单的语法描述数据结构,并通过工具生成特定语言的代码,从而简化序列化与反序列化操作。
ProtoBuf 的特点
跨语言、跨平台支持:支持多种编程语言(如 C++、Java、Python、Go 等),且在不同平台上兼容。
高效:数据以紧凑的二进制格式存储,序列化速度快,体积小,远小于 XML。
扩展性:可以通过添加新的字段来扩展数据结构,而不影响现有的字段,向后兼容性好。
结构化:采用强类型和字段编号定义,数据明确且易于解析。
ProtoBuf 的使用指南
创建和使用 ProtoBuf
1. 创建 .proto
文件
.proto
文件是用于描述数据结构的文件。在这里,我们以创建一个 contacts.proto
为例。
示例内容:
syntax = "proto3"; // 指定使用的语法版本
package contacts; // 定义包名,相当于命名空间
// 定义一个消息类型 PeopleInfo
message PeopleInfo {
string name = 1; // 姓名
int32 age = 2; // 年龄
// 字段编号不是赋值,而是唯一标识字段。编号不能重复。
}
2. 数据字段类型
ProtoBuf 提供了多种数据类型,主要分为标量数据类型和特殊类型。
标量数据类型:
string
:字符串,长度不能超过 2^32。int32
:有符号整型,变长编码。uint32
:无符号整型,变长编码。sint32
:有符号整型,用于负数的高效编码。fixed32
:无符号整型,定长编码。bytes
:二进制数据,可以包含任意字节序列,但不能超过2^32
。
特殊类型:
枚举类型:定义特定的枚举值。
嵌套消息类型:可以将一个消息类型嵌套在另一个消息类型中。
Map 类型:用于键值对数据的定义。
3. 字段编号
每个字段需要一个唯一编号,用于区分不同字段,这个编号在序列化时会被编码。
字段编号的选择:
编号范围:
1 - 2^29 - 1
,但编号19000 - 19999
不可用(预留给 ProtoBuf 内部使用)。编号
1 - 15
使用一个字节编码,适合频繁出现的字段。16 - 247
编号需要两个字节编码。注意:字段编号一旦使用,尽量不要更改,以免造成数据解析错误。
编译ProtoBuf 文件
chen@RE:~/l/m/p/p/fastTast|main⚡?
🤓☝️ protoc --cpp_out=. contacts.protoc
将会生成
chen@RE:~/l/m/p/p/fastTast|main⚡?
🤓☝️ ls 09:58:04
contacts.pb.cc contacts.pb.h contacts.proto
使用 -I 搜索目录
$ protoc -I fastTast/ --cpp_out=fastTast/
chen@RE:~/l/m/p/protobuf|main⚡?
🤓☝️protoc -I fastTast/ --cpp_out=fastTast/ contacts.proto 10:00:13
chen@RE:~/l/m/p/protobuf|main⚡?
🤓☝️tree 10:00:49
.
└── fastTast
├── contacts.pb.cc
├── contacts.pb.h
└── contacts.proto
2 directories, 3 files
序列化后的结果为二进制字节序列,而非文本格式
使用序列化
MAKEFILE
main:main.cc contacts.pb.cc
g++ -o Test main.cc contacts.pb.cc -std=c++17 -lprotobuf -labsl_log_internal_check_op -labsl_log_internal_message
.PHONY:clean
clean:
rm -f main
MAIN.CC
#include <iostream>
#include "contacts.pb.h"
int main()
{
std::string people_str;
// 对联系人进行 序列化 输出
{
contacts::PeopleInfo people;
people.set_name("C++");
people.set_age(74);
if(!people.SerializeToString(&people_str))
{
std::cerr<<" -- 反序列化失败 -- " << std::endl;
return -1;
}
std::cout << " -- 序列化成功 -- :" << people_str << std::endl;
}
std::cout << "/////////////////////////////////////////////////\
///////////////////////////////" << std::endl;
// 对联系人进行 反序列化 输出
{
contacts::PeopleInfo people;
if(!people.ParseFromString(people_str)){
std::cerr << " --- 反序列化失败 --- " << std::endl;
return -1;
}
std::cout << " --- 反序列化成功 --- " << std::endl
<< " -- 姓名 : " << people.name() << std::endl
<< " -- 年龄 : " << people.age() << std::endl;
}
}
-- 序列化成功 -- :
C++J
/////////////////////////////////////////////////////////////////
--- 反序列化成功 ---
-- 姓名 : C++
-- 年龄 : 74
ProtoBuf 相对于xml 和 Json 来说 被编译成二进制 破解成本增大 ,protobuf是相对安全
、
字段规则
singular: 消息中课包含 该字段的0次或1次
repeated 消息中可以包含字段的任意次数 0次数 或者多次重复值的数据会被保留,可以理解为定义一个数组
相关接口 [ProtoBuf]
SerializeToOstream
和 ParseFromIstream
这两个函数用于处理 Protobuf 数据的序列化和反序列化:
ParseFromIstream
: contacts.ParseFromIstream(&input)
从输入流读取二进制数据,并将其反序列化为内存中的 contacts
对象。
add_contacts
方法:
用途:用于向
Contacts
消息中添加一个新的PeopleInfo
消息。用法:
AddPeopleInfo(contacts.add_contacts());
这行代码创建了一个新的PeopleInfo
消息,并将其添加到contacts
对象的contacts
字段中。调用
contacts.add_contacts()
时,它会在contacts
列表中创建一个新的PeopleInfo
对象,并返回这个对象的指针(PeopleInfo*
类型的指针)。这个指针可以用来直接修改刚刚创建的
PeopleInfo
对象的内容。
二进制 转换 Ascii 码 工具 Hexdump
protoc -h
可以查看 protoc 命令的选项
--decode 查看二进制文件中的内容
使用方法
protoc --decode=contacts.Contacts contacts.proto < contacts.bin
🤓☝️ protoc --decode=contacts.Contacts contacts.proto < contacts.bin
contacts {
name: "reschen"
age: 999
phone {
number: "111111111111111111"
}
phone {
number: "111111111111111111111111"
}
phone {
number: "3333333333333333333333333333"
}
}
在反序列化的时候 枚举类型的默认值为 默认开始的枚举
Any 类
any类型 是一种 泛型类型
any接口
has_data()
:这是一个检查方法,用于确定Any
类型是否包含数据。Is<MessageType>()
:这是一个类型检查方法,它接受一个模板参数MessageType
,这个模板参数应该是一个 Protocol Buffers 消息类型。如果Any
对象中存储的消息类型与MessageType
匹配,那么Is<MessageType>()
方法会返回true
,否则返回false
。PackFrom()
方法用于将一个 protobuf 消息打包到Any
类型中any_type.mutable_data()->PackFrom(address);
UnpackTo()
方法用于从Any
类型中提取消息。