C++使用protobuf实现序列化与反序列化

2023-11-19

一、protobuf简介:

1.1 protobuf的定义:

protobuf是用来干嘛的?
protobuf是一种用于 对结构数据进行序列化的工具,从而实现 数据存储和交换

(主要用于网络通信中 收发两端进行消息交互。所谓的“结构数据”是指类似于struct结构体的数据,可用于表示一个网络消息。当结构体中存在函数指针类型时,直接对其存储或传输相当于是“浅拷贝”,而对其序列化后则是“深拷贝”。)

序列化: 将结构数据或者对象转换成能够用于存储和传输的格式。
反序列化: 在其他的计算环境中,将序列化后的数据还原为数据结构和对象。

从“序列化”字面上的理解,似乎使用C语言中的struct结构体就可以实现序列化的功能:将结构数据填充到定义好的结构体中的对应字段即可,接收方再对结构体进行解析。
在单机的不同进程间通信时,使用struct结构体这种方法实现“序列化”和“反序列化”的功能问题不大,但是,在网络编程中,即面向网络中不同主机间的通信时,则不能使用struct结构体,原因在于:
(1)跨语言平台,例如发送方是用C语言编写的程序,接收方是用Java语言编写的程序,不同语言的struct结构体定义方式不同,不能直接解析;
(2)struct结构体存在 内存对齐 和 CPU不兼容的问题。
因此,在网络编程中,实现“序列化”和“反序列化”功能需要使用通用的组件,如 Json、XML、protobuf 等。

1.2 protobuf的优缺点:

1.2.1 优点:

① 性能高效:
与XML相比,protobuf更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

② 语言无关、平台无关:
protobuf支持Java、C++、Python等多种语言,支持多个平台。

③ 扩展性、兼容性强:
只需要使用protobuf对结构数据进行一次描述,即可从各种数据流中读取结构数据,更新数据结构时不会破坏原有的程序。

Protobuf与XML、Json的性能对比:

测试10万次序列化:
在这里插入图片描述

测试10万次反序列化:
在这里插入图片描述

1.2.2 缺点:

① 自解释性较差,数据存储格式为二进制,需要通过 .proto 文件才能了解到内部的数据结构;
② 不适合用来对 基于文本的标记文档(如HTML) 建模。

1.3 protobuf中的数据类型限定修饰符:

protobuf 2 中有三种数据类型限定修饰符:

required, optional, repeated

required表示字段必选,optional表示字段可选,repeated表示一个数组类型。

其中, required 和 optional 已在 proto3 弃用了。

1.4 protobuf中常用的数据类型:

bool,		布尔类型

double,		64位浮点数
float,		32位浮点数

int32,		32位整数
int64,		64位整数
uint64,		64位无符号整数
sint32,		32位整数,处理负数效率更高
sint64,		64位整数,处理负数效率更高

string,		只能处理ASCII字符
bytes,		用于处理多字节的语言字符
enum,		枚举类型

二、protobuf的使用流程:

下载protobuf压缩包后,解压、配置、编译、安装,即可使用 protoc 命令 查看Linux中是否安装成功:

[root@linux] protoc --version
libprotoc 3.15.8

使用protobuf时,需要先根据应用需求编写 .proto 文件 定义消息体格式,例如:

syntax = "proto3";
package tutorial;

option optimize_for = LITE_RUNTIME;

message Person {
	int32 id = 1;
	repeated string name = 2;
}

其中,syntax 关键字表示使用的protobuf的版本,如不指定则默认使用 "proto2"package关键字 表示“包”,生成目标语言文件后对应C++中的namespace命名空间,用于防止不同的消息类型间的命名冲突。
(syntax单词字面含义:句法,句法规则,语构)

然后使用 protobuf编译器(protoc命令)将编写好的 .proto 文件生成 目标语言文件(例如目标语言是C++,则会生成 .cc 和 .h 文件),例如:

[root@linux] protoc -I=$SRC_DIR $SRC_DIR/xxx.proto --cpp_out=$DST_DIR

其中:
$SRC_DIR 表示 .proto文件所在的源目录;
$DST_DIR 表示生成目标语言代码的目标目录;
xxx.proto 表示要对哪个.proto文件进行解析;
--cpp_out 表示生成C++代码。

编译完成后,将会在目标目录中生成 xxx.pb.hxxx.pb.cc 文件,将其引入到我们的C++工程中即可实现使用protobuf进行序列化:
在C++源文件中包含 xxx.pb.h 头文件,在g++编译时链接 xxx.pb.cc源文件即可:

g++ main_test.cpp xxx.pb.cc -o main_test -lprotobuf

三、C++使用protobuf实现序列化的示例:

在protobuf源码中的 /examples 目录下有官方提供的protobuf使用示例:addressbook.proto

参考官方示例实现C++使用protobuf进行序列化和反序列化:

addressbook.proto :

syntax = "proto3";
package tutorial;

option optimize_for = LITE_RUNTIME;

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

	enum PhoneType {
		MOBILE = 0;
		HOME = 1;
		WORK = 2;
	}
	
	message PhoneNumber {
		string number = 1;
		PhoneType type = 2;
	}

	repeated PhoneNumber phones = 4;
}

生成的addressbook.pb.h 文件内容摘要:

namespace tutorial {
	class Person;
	class Person_PhoneNumber;
};

class Person_PhoneNumber : public MessageLite {
public:
	Person_PhoneNumber();
	virtual ~Person_PhoneNumber();
public:
	//string number = 1;
	void clear_number();
	const string& number() const;
	void set_number(const string& value);
	
	//int32 id = 2;
	void clear_id();
	int32 id() const;
	void set_id(int32 value);

	//string email = 3; 
	//...
};


add_person.cpp :

#include <iostream>
#include <fstream>
#include <string>
#include "pbs/addressbook.pb.h"
using namespace std;

void serialize_process() {
	cout << "serialize_process" << endl;
	tutorial::Person person;
	person.set_name("Obama");
	person.set_id(1234);
	person.set_email("1234@qq.com");

	tutorial::Person::PhoneNumber *phone1 = person.add_phones();
	phone1->set_number("110");
	phone1->set_type(tutorial::Person::MOBILE);

	tutorial::Person::PhoneNumber *phone2 = person.add_phones();
	phone2->set_number("119");
	phone2->set_type(tutorial::Person::HOME);

	fstream output("person_file", ios::out | ios::trunc | ios::binary);

	if( !person.SerializeToOstream(&output) ) {
		cout << "Fail to SerializeToOstream." << endl;
	}

	cout << "person.ByteSizeLong() : " << person.ByteSizLong() << endl;
}

void parse_process() {
	cout << "parse_process" << endl;
	tutorial::Person result;
	fstream input("person_file", ios::in | ios::binary);

	if(!result.ParseFromIstream(&input)) {
		cout << "Fail to ParseFromIstream." << endl;
	}

	cout << result.name() << endl;
	cout << result.id() << endl;
	cout << result.email() << endl;
	for(int i = 0; i < result.phones_size(); ++i) {
		const tutorial::Person::PhoneNumber &person_phone = result.phones(i);

		switch(person_phone.type()) {
			case tutorial::Person::MOBILE :
				cout << "MOBILE phone : ";
				break;
			case tutorial::Person::HOME :
				cout << "HOME phone : ";
				break;
			case tutorial::Person::WORK :
				cout << "WORK phone : ";
				break;
			default:
				cout << "phone type err." << endl;
		}
		cout << person_phone.number() << endl;
	}
}

int main(int argc, char *argv[]) {
	serialize_process();
	parse_process();
	
	google::protobuf::ShutdownProtobufLibrary();	//删除所有已分配的内存(Protobuf使用的堆内存)
	return 0;
}

输出结果:

[serialize_process]
person.ByteSizeLong() : 39
[parse_process]
Obama
1234
1234@qq.com
MOBILE phone : 110
HOME phone : 119

3.1 protobuf提供的序列化和反序列化的API接口函数:

class MessageLite {
public:
	//序列化:
	bool SerializeToOstream(ostream* output) const;
	bool SerializeToArray(void *data, int size) const;
	bool SerializeToString(string* output) const;
	
	//反序列化:
	bool ParseFromIstream(istream* input);
	bool ParseFromArray(const void* data, int size);
	bool ParseFromString(const string& data);
};

三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用。
序列化的API函数均为const成员函数,因为序列化不会改变类对象的内容, 而是将序列化的结果保存到函数入参指定的地址中。

3.2 .proto文件中的 option 选项:

.proto文件中的option选项用于配置protobuf编译后生成目标语言文件中的代码量,可设置为 SPEEDCODE_SIZELITE_RUNTIME 三种。
默认option选项为 SPEED,常用的选项为 LITE_RUNTIME。

三者的区别在于:

① SPEED(默认值):
表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。

② CODE_SIZE:
与SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,
通常用于资源有限的平台,如Mobile。

③ LITE_RUNTIME:
生成的代码执行效率高,同时生成代码编译后的所占用的空间也非常少。
这是以牺牲Protobuf提供的反射功能为代价的。
因此我们在C++中链接Protobuf库时仅需链接libprotobuf-lite,而非protobuf。

SPEED 和 LITE_RUNTIME相比,在于调试级别上,例如 msg.SerializeToString(&str); 在 SPEED 模式下会利用反射机制打印出详细字段和字段值,但是 LITE_RUNTIME 则仅仅打印字段值组成的字符串。

因此:可以在调试阶段使用 SPEED 模式,而上线以后提升性能使用 LITE_RUNTIME 模式优化。

最直观的区别是使用三种不同的 option 选项时,编译后产生的 .pb.h 中自定义的类所继承的 protobuf类不同:

//1. SPEED模式:(自定义的类继承自 Message 类)
// .proto 文件:
option optimize_for = SPEED;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::Message {};

//2. CODE_SIZE模式:(自定义的类继承自 Message 类)
// .proto 文件:
option optimize_for = CODE_SIZE;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::Message {};

//3. LITE_RUNTIME模式:(自定义的类继承自 MessageLite 类)
// .proto 文件:
option optimize_for = LITE_RUNTIME;
// .pb.h 文件:
class Person : public ::PROTOBUF_NAMESPACE_ID::MessageLite {};


四、protobuf的编码和存储方式:

① protobuf 将消息里的每个字段进行编码后,再利用TLV或者TV的方式进行数据存储;
② protobuf 对于不同类型的数据会使用不同的编码和存储方式;
③ protobuf 的编码和存储方式是其性能优越、数据体积小的原因。


参考内容:
https://blog.csdn.net/daaikuaichuan/category_9869251.html
https://blog.csdn.net/weixin_30945039/article/details/96611265

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++使用protobuf实现序列化与反序列化 的相关文章

  • 通过 SocketCAN 进行 boost::asio

    我正在考虑利用升压阿西奥 http www boost org doc libs 1 49 0 doc html boost asio html从a读取数据套接字CAN http en wikipedia org wiki SocketCA
  • 如何在 DataColumn.Expression 中使用 IF/ELSE 或 CASE?

    我有一个包含 1 列的表 状态 我想添加另一列名为 Action 的列 其值如下 如果 Status Yes 则 Action Go 否则 Action Stop 我使用以下代码添加到 操作 列中 但它不起作用 myDataTable Co
  • 如何保证对象只有一个线程

    我有以下代码 class Service public void start creates thread which creates window and goes to message loop void stop sends WM C
  • FileStream 构造函数和默认缓冲区大小

    我们有一个使用 NET 4 用 C 编写的日志记录类 我想添加一个构造函数参数 该参数可以选择设置文件选项 WriteThrough http msdn microsoft com en us library system io fileo
  • 为什么在创建矩阵类时使用向量不好?

    对于我的矩阵类 我做了 template
  • 更改 Qt OpenGL 窗口示例以使用 OpenGL 3.3

    我正在尝试更改 Qt OpenGL 示例以使用更现代的 opengl 版本 330 似乎合适 所以我做了 在 main cpp 上设置版本和配置文件 设置着色器版本 更改着色器以使用统一 它现在构建没有任何错误 但我只看到一个空白窗口 我错
  • 在 Xamarin 中隐藏软键盘

    如何隐藏软键盘以便在聚焦时显示Entry在 Xamarin forms 便携式表单项目中 我假设我们必须为此编写特定于平台的渲染器 但以下内容不起作用 我创建自己的条目子类 public class MyExtendedEntry Entr
  • EF Core 通过完全替换断开集合导航属性的更新

    使用 EF Core 5 0 我有一个 SPA 页面 可以加载Group实体及其集合Employee来自 API 的实体 var groupToUpdate await context Groups Include g gt g Emplo
  • 防止 boost::asio::io_context 在空轮询调用时停止

    此代码调用发布的句柄 boost asio io context ioc boost asio post ioc std cout lt lt lol lt lt std endl ioc poll 而这并没有 boost asio io
  • 类特定的新删除运算符是否必须声明为静态

    标准中是否要求类特定的 new new delete 和 delete 是静态的 我可以让它们成为非静态成员运算符吗 为什么需要它们是静态的 它们被隐式声明为静态 即使您没有键入 static
  • ASP.Net Core 内容配置附件/内联

    我正在从 WebAPI 控制器返回一个文件 Content Disposition 标头值自动设置为 附件 例如 处置 附件 文件名 30956 pdf 文件名 UTF 8 30956 pdf 当它设置为附件时 浏览器将要求保存文件而不是打
  • 动态生成的控件 ID 返回为 NULL

    我可以在 Page PreInit 函数中创建动态控件 如何检索控件及其 ID 我的 C 代码用于创建动态控件之一 var btn new WebForms Button btn Text btn ID Addmore btn Click
  • 从 WebBrowser 控件 C# 获取滚动值

    我试图在 WebBrowser 控件中获取网页的 Y 滚动索引 但无法访问内置滚动条的值 有任何想法吗 对于标准模式下的 IE 使用文档类型 正如你所说 scrollTop是的财产元素 而不是 HtmlDocument htmlDoc th
  • C++ php 和静态库

    我创建了一个library a 其中包含 cpp 和 h 文件 其中包含很多类 嵌套类和方法 我想在 php 示例中包含这个静态库并尝试使用它 我想提一下 我是 php 新手 我已经在 test cpp 文件中测试了我的 libray a
  • 如何分析组合的 python 和 c 代码

    我有一个由多个 python 脚本组成的应用程序 其中一些脚本正在调用 C 代码 该应用程序现在的运行速度比以前慢得多 因此我想对其进行分析以查看问题所在 是否有工具 软件包或只是一种分析此类应用程序的方法 有一个工具可以将 python
  • 在 EnvDTE 中调试时捕获 VS 局部变量

    是否可以使用 EnvDTE 进行 vsix Visual Studio 扩展来捕获本地和调试窗口使用的调试数据 或者可以通过其他方法吗 我想创建一个自定义的本地窗口 我们可以修改它以根据需要显示一些较重的内容 而无需为高级用户牺牲原始的本地
  • 我可以让 ungetc 取消阻止阻塞的 fgetc 调用吗?

    我想在收到 SIGUSR1 后使用 ungetc 将 A 字符重新填充到标准输入中 想象一下我有充分的理由这样做 调用 foo 时 stdin 中的阻塞读取不会被收到信号时的 ungetc 调用中断 虽然我没想到它会按原样工作 但我想知道是
  • 使用restsharp序列化对象并将其传递给WebApi而不是序列化列表

    我有一个看起来像的视图模型 public class StoreItemViewModel public Guid ItemId get set public List
  • 在简单注入器中解析具有自定义参数的类

    我正在使用以下命令创建 WPF MVVM 应用程序简易注射器作为 DI 容器 现在 当我尝试从简单注入器解析视图时遇到一些问题 因为我需要在构造时将参数传递到构造函数中 而不是在将视图注册到容器时 因此这不是适用的 简单注入器将值传递到构造
  • C++0x中disable_if在哪里?

    Boost 两者都有enable if and disable if 但 C 0x 似乎缺少后者 为什么它被排除在外 C 0x 中是否有元编程工具允许我构建disable if按照enable if 哦 我刚刚注意到std enable i

随机推荐

  • AIGC产生内容的版权到底归属于谁?

    随着ChatGPT的火热 AIGC 人工智能生成内容 产生的内容越来越被大众使用 但是 有一个问题一直困扰着大家 包括放牛娃 那就是 AIGC产生的内容 有版权吗 版权到底归属于谁 01 什么是AIGC AIGC是指人工智能生成内容 也称为
  • 专利与论文-7:专利在哪儿?如何发现专利?思维分析方法

    前言 专利的最核心的要素 不在于如何熟悉专利申请的流程 也不在于如何写一份符合法律要求的 能够真正保护自己权利的专利申请文件 也不在于专利申请需要花多少钱 而在于如何产生一个真正有价值的专利本身 这是一个思维创造的过程 是拥有自己专利的最难
  • 火爆全网的人工智能聊天机器人使用教程

    最近 大家有没有被一款人工智能聊天机器人所刷屏呢 记得之前元宇宙刚出来的时候 也是极其火爆 虽说这二者是不同的性质 但是都是代表着当下互联网发展之迅速 一 简介 百度百科中给出的解释 美国 开放人工智能研究中心 研发的聊天机器人程序 于20
  • 自定义窗口部件,QT之为什么要delete ui,而其它new对象不用delete

    QT的父子对象机制是在 QWidget和QOject中实现的 当我们使用父对象来创建一个对象的时候 父对象会把这个对象添加到自己的子对象列表中 当这个父对象被删除的时候 它会遍历它的子对象类表并且删除每一个子对象 然后子对象们自己再删除它们
  • android c#websocket传送图片

    这个失败了 传一张可以 一直传就报错 public static void startHttpClientWebSocket AsyncHttpClient getDefaultInstance websocket ws 192 168 2
  • 【100%通过率 】【华为OD机试c++/java/python】最长的密码【 2023 Q1A卷

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 最长的密码 小王在进行游戏大闯关 有一个关卡需要输入一个密码才能通过 密码获得的条件如下 在一个密码本中 每一页都有一个由26个小写字母组成的若
  • Redis可视化工具Redis Desktop Manager使用

    转载于 https www cnblogs com aimu p 7047671 html https www cnblogs com stone w p 4757578 html Redis可视化工具Redis Desktop Manag
  • BRD MRD PRD提纲

    BRD MRD PRD应该怎么写 提纲如下 2011 06 30 20 38 06 转载 标签 brd mrd prd 杂谈 分类 学习 根据情况 总结一下 文档类型 需要做的工作 提纲如下 要达到的目标 BRD阶段 一 市场分析 二 销售
  • C++中的继承

    目录 1 继承的概念及定义 1 1继承的概念 1 2 1继承格式 1 2 2继承关系和访问限定符 1 2 3继承基类成员访问方式的变化 2 基类和派生类对象赋值转换 3 继承中的作用域 同名成员 同名函数 4 派生类的默认成员函数 5 继承
  • 感冒交叉感染

    给没有感染的家人服用板蓝根颗粒 窗户每天要打开同风至少在半小时 平时可以用84消毒液拖地或清洗抹布等 病人的生活用品单方 如果条件允许可以按消毒灯 平时让家人多喝水 适当运动 感染者要带口罩 餐具隔离 室内用醋蒸薰消毒 经常开窗通风 未感者
  • QRCode简单生成二维码

    QRCode简单生成二维码 1 导包 2 jsp的body里 div div
  • 通讯录_Php通讯录系统

    点击上面 蓝字 关注我们 Php通讯录系统 address list 主页 通讯录系统 管理员登录 登录 注册 后台 通讯录系统 编辑 代码 include conn conn php url SERVER REQUEST URI url
  • Maven settings.xml文件中各配置项的详细说明

    1 Maven settings xml文件中各配置项的说明 localRepository 本地仓库 指定Maven本地仓库的路径 默认情况下 它位于用户主目录下的 m2文件夹中
  • Windows系统设置每天自动备份指定文件并自动删除七天前的文件(脚本+Windows任务计划)

    Windows系统设置每天自动备份指定文件并自动删除七天前的文件 脚本 Windows任务计划 在生活中和工作中有时候为了避免电脑宕机导致文件丢失常常会使用一些方式去备份文件 今天小编给大家介绍一个方法 用于自动备份指定文件 并删除N天前的
  • 射击游戏c语言源码,射击游戏 (C++代码)

    解题思路 路 一开始不理解假定A和B都足够聪明 采取让自己获胜概率尽量高的策略 你的任务是计算出A获胜的概率 其实就是说在任意一种方式下都是取得胜利的最大概率 所以要从所有可能的情况 1 6行 从左or从右 里取一个最大的 那么直接去dfs
  • Redis 的五种基本类型(实战篇)

    良心公众号 关注不迷路 Redis 是一个速度非常快的非关系型数据库 它可以存储键 key 与 5 种不同类型的值 value 之间的映射 可以将存储在内存的键值对数据持久化到硬盘 可以使用复制特性来扩展读性能 还可以使用客户端分片来扩展性
  • 【python爬虫】爬取淘宝网商品信息

    相信学了python爬虫 很多人都想爬取一些数据量比较大的网站 淘宝网就是一个很好的目标 其数据量大 而且种类繁多 而且难度不是很大 很适合初级学者进行爬取 下面是整个爬取过程 第一步 构建访问的url 构建访问的url goods 鱼尾裙
  • 基于ensp的校园无线网络仿真实验

    写在前面 本项目是我们大三的一堂实践课作业内容 之前发过一次但是瞬间反响很好所以怕被抄袭删掉了555 现在成绩也出了尘埃落定发出来和大货分享分享qwq 当然还是仅供学习参考 禁止洗稿噢 摘要 本论文研究了一个基于有线和无线网络的校园网项目的
  • Jmeter完整的性能测试流程图,接口测试步骤

    第零 300G全套最新软测视频教程分享 链接 https pan baidu com s 17jkyGn Wm zC6QQLkWjrsw 提取码 o94n 第一 测试相关系统准备 1 验证基本系统功能后 性能测试在哪个阶段开始介入 通常 最
  • C++使用protobuf实现序列化与反序列化

    一 protobuf简介 1 1 protobuf的定义 protobuf是用来干嘛的 protobuf是一种用于 对结构数据进行序列化的工具 从而实现 数据存储和交换 主要用于网络通信中 收发两端进行消息交互 所谓的 结构数据 是指类似于