序列化和反序列化TCP粘包问题

news/2025/3/15 19:38:52/

目录

一、什么是序列化和反序列化?

二、利用Jsoncpp实现序列化和反序列化

1.序列化 

(1)使用 Json::Value 的 toStyledString 方法

(2)使用 Json::StreamWriterf 方法

(3)使用 Json::FastWriterff 方法

2.反序列化

(1)使用 Json::Reader 方法

三、数据封装(消息定界)——解决TCP通信粘包问题


一、什么是序列化和反序列化?

我们之前进行网络编程时,都是在应用层编写代码,处理的都是一些复杂的数据结构,如结构体、对象等等,再将这些数据通过socket接口传输到网络中。但是前提情况是服务端和客户端都是在相同的平台环境中。实际上,发送这些数据时,还利用序列化协议如Protocol Buffers、MessagePack等将数据进行序列化转换。

为什么不直接将这些数据传输过去呢?因为发送方和接收方环境可能不相同,内存布局不同,例如直接将结构体发送给接收方,可能存在填充字节等问题。另外发送发和接收方的编程语言不同也会有影响,例如一个C++编写的服务端发送数据给一个Python编写的客户端,二者数据结构的定义都不相同,无法正确接收。

发送方将复杂的数据结构统一转换为结构化的数据,再发送给到网络中,这就是序列化;接收方接收到结构化数据后根据序列化与反序列化协议,再将结构化数据转换为复杂的数据结构,这就是反序列化。序列化和反序列化可以解决数据跨系统、跨语言、跨平台传输一致性问题。

二、利用Jsoncpp实现序列化和反序列化

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中。
Linux安装Jsoncpp命令:

sudo apt-get install libjsoncpp-dev #ubuntu
sudo yum install jsoncpp-devel #centos

1.序列化 

(1)使用 Json::Value 的 toStyledString 方法

优点:将 Json::Value 对象直接转换为格式化的 JSON 字符串

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
std::string s = root.toStyledString();
std::cout << s << std::endl;
return 0;
} //{
//    "name" : "joe",
//    "sex" : "男"
//}
(2)使用 Json::StreamWriterf 方法

优点:提供了更多的定制选项, 如缩进、 换行符等

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}//{
//    "name" : "joe",
//    "sex" : "男"
//}
(3)使用 Json::FastWriterff 方法

优点:比 StyledWriter 更快, 因为它不添加额外的空格和换行符

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::FastWriter writer;//Json::StyledWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}//{
//    "name" : "joe",
//    "sex" : "男"
//}

2.反序列化

(1)使用 Json::Reader 方法

优点:提供详细的错误信息和位置, 方便调试

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main() 
{// JSON 字符串std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 从字符串中读取 JSON 数据bool parsingSuccessful = reader.parse(json_string,root);if (!parsingSuccessful) {// 解析失败, 输出错误信息std::cout << "Failed to parse JSON: " <<reader.getFormattedErrorMessages() << std::endl;return 1;}// 访问 JSON 数据std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 输出结果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
}//Name: 张三
//Age: 30
//City: 北京

三、数据封装(消息定界)——解决TCP通信粘包问题

由于TCP是面向字节流的协议,传输的数据时不会自动维护消息的边界,发送方多次发送的数据可能会被合并接收,或者发送方写入发送缓冲区的数据会被拆分为多次发送,这就会导致接收方从接收缓冲区中读取的数据可能不是一条完整的数据,可能是多条数据组合在一起或者仅仅是一条完整数据的一部分。这个问题就是粘包问题。

为了解决粘包问题,发送方在对发送数据使用Jsoncpp序列化之后,要对Json数据加上固定长度消息头或者分割界定符来为维护数据边界,同样的接收方接收到消息后,要先从消息中提取出一条完整的数据(即去除数据边界),再对数据进行反序列化。

static const std::string sep = "\r\n";
// 设计一下协议的报头和报文的完整格式
// "len"\r\n"{json}"\r\n --- 完整的报文, len 有效载荷的长度!
// \r\n: 区分len 和 json 串
// \r\n: 暂是没有其他用,打印方便,debug
// 添加报头
std::string Encode(const std::string &jsonstr)
{int len = jsonstr.size();std::string lenstr = std::to_string(len);return lenstr + sep + jsonstr + sep;
}
// 不能带const
// "le
// "len"
// "len"\r\n
// "len"\r\n"{json}"\r\n (]
// "len"\r\n"{j
// "len"\r\n"{json}"\r\n"len"\r\n"{
// "len"\r\n"{json}"\r\n
// "len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r
std::string Decode(std::string &packagestream)
{// 分析auto pos = packagestream.find(sep);if (pos == std::string::npos)return std::string();std::string lenstr = packagestream.substr(0, pos);int len = std::stoi(lenstr);// 计算一个完整的报文应该是多长??int total = lenstr.size() + len + 2 * sep.size();if (packagestream.size() < total)return std::string();// 提取std::string jsonstr = packagestream.substr(pos + sep.size(), len);packagestream.erase(0, total);return jsonstr;
}

http://www.ppmy.cn/news/1579386.html

相关文章

机器学习或深度学习中---保存和加载模型的方法

在机器学习或深度学习中&#xff0c;训练好的模型可以通过多种方式保存和加载&#xff0c;以便在后续使用中进行推理&#xff08;预测&#xff09;或进一步训练。以下是常见的保存和加载模型的方法&#xff0c;以 Python 中的常见库&#xff08;如 scikit-learn、TensorFlow、P…

Android 自定义数字键盘实现教程

在 Android 应用中&#xff0c;系统默认的键盘可能无法满足特定需求&#xff08;如仅支持数字输入、自定义布局等&#xff09;。本文将详细介绍如何实现一个自定义数字键盘&#xff0c;并提供完整的代码示例。 实现步骤 1. 创建自定义键盘布局 首先&#xff0c;我们需要定义一…

适合企业内训的AI工具实操培训教程(37页PPT)(文末有下载方式)

详细资料请看本解读文章的最后内容。 资料解读&#xff1a;适合企业内训的 AI 工具实操培训教程 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术迅速发展&#xff0c;深度融入到各个领域&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;更是成…

linux root丢失修改密

在RHEL7下重置密码 第一种方式&#xff1a;光驱进入急救模式 //做之前最好 selinuxdisabled Conntinue 然后chroot /mnt/sysimag 然后编辑/etc/shadow文件 第二种方式&#xff1a; 1&#xff1a;编辑启动菜单按e,找到linux16行&#xff0c;在行尾加入 init/bin/sh,同时在…

蓝桥杯 之 回溯之充分剪枝

文章目录 买瓜最大数字 在蓝桥杯当中&#xff0c;对于回溯是属于一个必考的问题&#xff0c;但是除了回溯的几个基本的问题&#xff0c;如果通过剪枝来提前删去无效的分支&#xff0c;以大大减少时间复杂度是需要我们进一步思考的问题&#xff01;回溯的基本问题&#xff1a; 回…

u8g2的STM32移植,附上一些图片取模和字体取模的方式。

u8g2的STM32移植&#xff0c;附上一些图片取模和字体取模的方式。 STM32移植取模方式制作自己的字体库 STM32移植 准备一个正常运行的STM32模板下载 U8g2 的源码和 U8g2 的 STM32 实例模板 源码&#xff1a;https://github.com/olikraus/u8g2 STM32 实例模板&#xff1a;https…

[项目]基于FreeRTOS的STM32四轴飞行器: 二.项目搭建及移植FreeRTOS

基于FreeRTOS的STM32四轴飞行器: 二.项目搭建及debug模块 一.项目搭建二.移植FreeRTOS 一.项目搭建 先配置SYS系统滴答定时器来源为默认&#xff0c;因为其他定时器用来驱动电机了只能与FreeRTOS共用&#xff1a; 之后选择RCC配置芯片的时钟来源&#xff1a; 配置时钟树&am…

MySQL -- 表的约束

概念引入&#xff1a;真正的约束表字段的是数据类型&#xff0c;但是数据类型的约束方式比较单一的&#xff0c;所以需要一些额外的一些约束&#xff0c;用于表示数据的合法性&#xff0c;在只有数据类型一种约束的情况下&#xff0c;我们比较难保证数据是百分百合法。通过添加…