突破编程_C++_网络编程(一种高性能处理 TCP 粘包问题的方法)

1 前言

在“突破编程_C++_网络编程(Windows 套接字(处理 TCP 粘包问题))”一文中,已经讲解了 TCP 粘包问题,并且给出了样例代码。但是该样例代码的核心是使用队列(std::queue)做报文的处理。

std::queue 是 C++ 标准模板库(STL)中的一个容器适配器,它提供了一种先进先出(FIFO)的数据结构。在 STL 中,std::queue 并不直接存储元素,而是依赖于一个底层容器来管理元素的存储,这个底层容器通常是 std::deque(双端队列)或者 std::list(双向链表),具体取决于实现。

默认情况下,std::queue 会使用 std::deque 作为底层容器:这是最常见的实现方式。std::deque 提供了从两端快速插入和删除元素的能力,这与 std::queue 的操作特性非常吻合。在 std::deque 上,std::queue 的 push() 操作对应于 std::deque 的 push_back(),而 pop() 对应于 std::deque 的 pop_front()。

如果使用 std::queue 作为处理 TCP 粘包问题,所有接收到的 TCP 数据会先存入队列中,然后再一个一个拿出来处理。一般情况下,这种处理模式是可行的,但是当报文的字节数很大时,这种模式的效率会比较低,所以本文会实现一种高性能的处理方式。

2 回顾一下 TCP 粘包问题

TCP(传输控制协议)是一种面向流的协议,它不保留数据包边界。在TCP连接中,数据被看作是一连串无结构的字节流。TCP 粘包问题指的是接收方在接收数据时,由于发送方发送的数据包较小,或者接收方处理较慢等原因,导致多个数据包粘在一起,形成一个大的数据块,从而使得接收方无法辨认出各个数据包的边界。

2.1 粘包产生的原因

(1)发送方原因:

  • 发送数据包较小,尤其是小于TCP协议的MSS(最大报文段长度)。
  • 发送数据包的时间间隔太短,导致接收方来不及处理。

(2)接收方原因:

  • 接收处理速度慢,导致多个数据包到达后才开始处理。
  • 接收缓冲区大小设置不当,可能过大或过小。

(3)网络原因:

  • 网络延迟或拥塞,导致数据包传输时间不一致。

2.2 粘包问题的影响

TCP 粘包问题对网络通信的影响是多方面的,它可能会导致数据的不完整、错误解析、性能下降甚至安全问题。以下是 TCP 粘包问题可能带来的一些具体影响:

  • 数据完整性受损:由于接收方无法准确识别数据包边界,可能会导致数据包被错误地合并或拆分,从而造成数据丢失或重复。

  • 错误解析:粘包可能导致接收方错误地解析数据,比如将两个数据包的内容错误地解释为一个数据包,或者将一个数据包的内容错误地解释为两个数据包。

  • 应用逻辑错误:在某些应用中,数据包的内容和顺序是非常重要的。粘包问题可能会导致应用逻辑处理错误,比如在聊天应用中,消息的顺序可能会被打乱。

  • 性能下降:为了处理粘包问题,接收方可能需要额外的缓冲区来暂存数据,并进行边界检测,这会增加CPU和内存的使用,从而降低系统的整体性能。

  • 延迟增加:在某些情况下,为了等待更多的数据以确定数据包的边界,接收方可能会延迟处理已经接收到的数据,这会增加处理延迟。

  • 资源浪费:由于粘包问题,接收方可能需要分配更大的缓冲区来暂存数据,这可能会导致内存资源的浪费。

  • 安全问题:粘包问题可能会被恶意利用,比如通过发送特制的数据包来破坏接收方的缓冲区,从而导致缓冲区溢出等安全问题。

  • 协议复杂性增加:为了解决粘包问题,可能需要在应用层定义额外的协议来标识数据包的边界,这会增加协议的复杂性。

  • 兼容性问题:不同的系统和应用可能采用不同的方法来处理粘包问题,这可能会导致兼容性问题。

  • 调试困难:粘包问题可能会使得网络通信的调试变得更加困难,因为错误可能不容易被发现和定位。

2.3 解决粘包问题的方法

(1)固定长度消息:

每个消息都发送固定长度的数据,接收方按照固定长度进行读取和处理。

**(2)消息头和消息体:

消息分为消息头和消息体,消息头中包含消息体的长度信息,接收方根据消息头中的长度来确定消息体的边界。

**(3)使用特殊字符或字节序列:

在消息体中使用特殊的字符或字节序列作为消息边界的标识。

**(4)应用层协议:

定义应用层协议,明确消息的开始和结束,例如使用 HTTP 协议中的 Content-Length 字段。

**(5)使用缓冲区处理:

接收方使用缓冲区暂存接收到的数据,当满足一个完整消息的条件时再进行处理。

**(6)使用 TCP 的紧急数据或带外数据:

通过发送紧急指针或带外数据来标记消息的结束。

3 一种高性能的 TCP 粘包问题的处理方式

本方式采用使用消息头(Header)+消息体长度(Length)+消息体(Body)的方法来做处理:

  1. 消息头设计:设计一个固定长度的消息头结构,通常包含消息体的长度信息。消息头的长度是固定的,以便于接收方能够快速地识别出消息头并从中读取消息体的长度。

  2. 消息体长度:在消息头中,最关键的信息是消息体的长度,这个长度值告诉接收方接下来需要读取多少字节的数据来构成一个完整的消息体。

  3. 消息体发送:发送方在发送消息体之前,先发送包含消息体长度的消息头。这样,接收方在接收到消息头后,就能够知道接下来需要读取多少字节的数据。

  4. 接收处理

    • 接收方首先读取消息头,解析出消息体的长度。
    • 根据消息头中的长度信息,接收方分配一个足够大的缓冲区来存储整个消息体。
    • 接收方接着从TCP流中读取指定长度的消息体数据,直到读取完整个消息体。
  5. 连续读取:在读取完一个完整消息后,接收方再次读取消息头,以确定下一个消息的边界,继续处理后续的消息。

  6. 缓冲区管理:为了处理可能的粘包情况,接收方可能需要使用一个缓冲区来暂存接收到的数据。当缓冲区中的数据足以构成一个完整的消息时,就从缓冲区中提取出消息并处理,然后将剩余的数据留在缓冲区中,等待构成下一个消息。

  7. 错误处理:在设计协议时,还应考虑错误处理机制,比如如果接收到的消息头中的长度信息不合理(如长度为负数或过大),则需要采取相应的错误处理措施。

通过这种方式,即使在TCP流中数据包被粘在一起,接收方也能够准确地识别出每个消息的边界,从而有效地解决了粘包问题。这种方法简单、高效,且易于实现,因此在处理TCP粘包问题时被广泛采用。

(1)定义数据缓冲区

#include <iostream>  
#include <string>  
#include <sstream>
#include <vector>  
#include <deque>  
#include <tuple>  
#include <memory>  

using namespace std;

std::deque<tuple<unique_ptr<char[]>, uint64_t>> remainingDatas;

(2)定义工具函数,用来显示报文

string binaryStringToHex(string& binaryStr, string strPre, string strSplit)
{
	string ret;
	static const char *hex = "0123456789ABCDEF";
	uint64_t offset = 0;
	for (auto c : binaryStr)
	{
		ret.append(strPre);

		ret.push_back(hex[(c >> 4) & 0xf]); //取二进制高四位
		ret.push_back(hex[c & 0xf]);        //取二进制低四位

		if (offset < binaryStr.length() - 1)
		{
			ret.append(strSplit);
		}

		offset++;
	}
	return ret;
}

string binaryCharToHex(const char* data, uint64_t len, string strPre, string strSplit)
{
	string strBinaryVal = string(data, len);
	return binaryStringToHex(strBinaryVal, strPre, strSplit);
}

(3)TCP 粘包问题处理函数

void revMsg(const char *data, uint64_t len)
{
	if (0 == len) {
		return;
	}
	unique_ptr<char[]> dataTmp = make_unique<char[]>(len);
	memcpy(dataTmp.get(), data, len);
	remainingDatas.push_back(tuple<unique_ptr<char[]>, uint64_t>(move(dataTmp), len));

	while (remainingDatas.size() > 0) {
		auto remainingDataPtr = get<0>(remainingDatas[0]).get();
		if (0x20 != remainingDataPtr[0]) {
			remainingDatas.clear();
			return;
		}

		if (1 == get<1>(remainingDatas[0]) && remainingDatas.size() <= 1) {
			return;
		}

		uint8_t msgLen = 0;

		uint64_t totalLen = 0;
		for (auto& remainingData : remainingDatas) {
			totalLen += get<1>(remainingData);
		}

		if (1 == get<1>(remainingDatas[0])) {
			remainingDataPtr = get<0>(remainingDatas[1]).get();
			msgLen = (uint8_t)remainingDataPtr[0];
		}
		else {
			msgLen = (uint8_t)remainingDataPtr[1];
		}

		if (msgLen + 2 > totalLen) {
			return;
		}

		unique_ptr<char[]> totalData = make_unique<char[]>(totalLen);
		uint64_t totalDataOffset = 0;
		for (auto& remainingData : remainingDatas) {
			uint64_t lenTmp = get<1>(remainingData);
			memcpy(&(totalData.get())[totalDataOffset], get<0>(remainingData).get(), lenTmp);
			totalDataOffset += lenTmp;
		}
		remainingDatas.clear();

		uint64_t offset = 2;
		uint64_t lastMsgOffset = 0;			// 最后一帧完整报文的最后一个字节偏移

		while (offset + msgLen <= totalLen) {

			uint64_t revMsgLen = msgLen + 2;
			unique_ptr<char[]> revMsg = make_unique<char[]>(revMsgLen);
			memcpy(revMsg.get(), &(totalData.get())[offset - 2], revMsgLen);

			// 打印处理粘包后的一帧完整报文
			auto strMsg = binaryCharToHex(revMsg.get(), revMsgLen, "", " ");
			printf("[recive] %s\n", strMsg.c_str());

			offset += msgLen;
			lastMsgOffset = offset;

			if (offset + 2 < totalLen) {
				if (0x20 != totalData[offset]) {
					return;
				}
				offset++;
				msgLen = (uint8_t)totalData[offset];
				offset++;
			}
		}

		if (lastMsgOffset < totalLen) {
			uint64_t lastRemainLen = totalLen - lastMsgOffset;
			unique_ptr<char[]> lastRemainData = make_unique<char[]>(lastRemainLen);
			memcpy(lastRemainData.get(), &(totalData.get())[lastMsgOffset], lastRemainLen);
			remainingDatas.push_back(tuple<unique_ptr<char[]>, uint64_t>(move(lastRemainData), lastRemainLen));
		}
	}
}

(4)使用模拟的字节流数据做测试

std::vector<std::string> split(const std::string& str, const char delimiter)
{
	std::vector<std::string> tokens;
	std::istringstream tokenStream(str);
	std::string token;

	while (std::getline(tokenStream, token, delimiter))
	{
		tokens.push_back(token);
	}

	return tokens;
}

tuple<unique_ptr<char[]>, size_t> getBytesFromStr(string str)
{
	vector<string> strs = split(str, ' ');
	if (strs.size() > 0)
	{
		unique_ptr<char[]> bytes(new char[strs.size()]);
		for (size_t i = 0; i < strs.size(); i++)
		{
			int val = strtol(strs[i].c_str(), nullptr, 16);
			bytes[i] = (char)val;
		}
		return tuple<unique_ptr<char[]>, size_t>(move(bytes), strs.size());
	}
	else
	{
		return tuple<unique_ptr<char[]>, size_t>(unique_ptr<char[]>(nullptr), 0);
	}
}

int main() {
	// 模拟TCP接收数据  
	string str = "20";
	auto res = getBytesFromStr(str);
	revMsg((get<0>(res)).get(), get<1>(res));

	str = "04 02 00 00 01 20 04 02 00 00 02 20 04 02 00";
	res = getBytesFromStr(str);
	revMsg((get<0>(res)).get(), get<1>(res));

	str = "00 03 20 04 02 00 00 04 20 04 02 00";
	res = getBytesFromStr(str);
	revMsg((get<0>(res)).get(), get<1>(res));
	
	return 0;
}

上面代码的输出为:

[recive] 20 04 02 00 00 01
[recive] 20 04 02 00 00 02
[recive] 20 04 02 00 00 03
[recive] 20 04 02 00 00 04

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/569042.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW和MES系统的智能化车间数据对接

LabVIEW和MES系统的智能化车间数据对接 随着工业4.0时代的到来&#xff0c;智能制造成为推动制造业高质量发展的重要手段。其中&#xff0c;数字化车间作为智能制造的重要组成部分&#xff0c;其设计与实现至关重要。在数字化车间环境下&#xff0c;如何利用LabVIEW软件与MES系…

解析SoC芯片:构建智能设备的核心技术

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

linux磁盘原理

在linux系统中&#xff0c;对磁盘进行管理与windows系统类似&#xff0c;都要先分区&#xff0c;格式化&#xff0c;创建文件系统&#xff0c;挂载目录&#xff0c;数据写入

【PHP开发工程师详细讲解分析】——网站注册账号(头像的上传操作),让自己喜欢的头像更换畅通无阻

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

便携式应急指挥箱规格参数

概况: 微缩型的无线视频音频传输的机动挥所。体积小、重量轻、公配电方便、携带便携、功能齐全。可进行单兵作战&#xff0c;通过此无线音频视频传输的指挥箱能完成现场图像、语音、数据的采集等功能&#xff0c;可以通过5G/4G/WIFI等多种无线网络完成传输的需求&#xff0c;或…

计算机网络相关知识总结

一、概述 计算机网络可以极大扩展计算机系统的功能机器应用范围&#xff0c;提高可靠性&#xff0c;在为用户提供放方便的同时&#xff0c;减少了整体系统费用&#xff0c;提高性价比。 计算机网络的功能主要有&#xff1a;1. 数据共享&#xff1b;2. 资源共享&#xff1b;3. 管…

echart坑

echart坑 原因&#xff1a; 引用了echarts里面的init方法显示没有定义 解决的方法 将import echarts from echarts 的引入方式改为&#xff1a; import * as echarts from echarts

【vue2】实现微信截图(复制图片)在项目内可粘贴

需求 后台管理在上传图片地方需要将复制的图片粘贴上传 一、添加事件 在原有上传组件的基础上添加 paste事件 二、方法 onPaste(e) {const items (e.clipboardData || window.clipboardData).items;let blob null;for (let i 0; i < items.length; i) {if (items[i].ty…

学习Rust的第10天:枚举和模式匹配

今天我们来看看一个类似的概念 enums 。 Enums: We saw that in Rust, enums are data types that list possible values, giving a simple and type-safe mechanism to describe alternatives. We looked at how to create enums and use them to represent similar possibili…

Dropout Feature Ranking for Deep Learning Models

摘要 深度神经网络( deep neural networks&#xff0c;DNNs )在多个领域取得了最新的研究成果。不幸的是&#xff0c;DNNs因其不可解释性而臭名昭著&#xff0c;从而限制了其在生物和医疗保健等假说驱动领域的适用性。此外&#xff0c;在资源受限的环境下&#xff0c;设计依赖…

沐风老师3dMax万有引力插件ToGround使用方法详解

3dMax万有引力插件ToGround使用教程 3dMax万有引力插件ToGround&#xff0c;用于在复杂地形&#xff08;曲面&#xff09;上将对象放置在适当高度的实用工具。例如&#xff1a;将大量的人、植物和汽车快速放置在一个街道、公园和小跑道高度不同的区域尤其有用。 【适用版本】 …

android openGL ES详解

1、渲染线程与主线程的通信 两个线程之间的通信可以用如下方法: 在主线程中的 GLSurfaceView 实例可以调用 queueEvent( &#xff09;方法传递一个 Runnable 给后台渲染线程&#xff0c;渲染线程可以调用 Activity 的 runOnUIThread()来传递事件 (event) 给主线程。 2、顶点…

SQLite FTS3 和 FTS4 扩展(三十二)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite 的命令行 Shell(三十一&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 概述 FTS3 和 FTS4 是 SQLite 虚拟表模块&#xff0c;允许用户执行 对一组文档进行全文搜索。最常见&#xff08;和最有效…

Linux之yum和vim的使用

一、yum的使用 yum 后面跟install要安装的文件名&#xff1a; 若你要安装的文件已经存在&#xff0c;则会出现&#xff1a; 要删除文件&#xff1a; yum remore文件名即可删除 在我们安装完lrzsz之后&#xff0c;可以用rz指令和sz指令&#xff1a; rz指令可以从window窗口中…

开源模型应用落地-chatglm3-6b-集成langchain(十)

一、前言 langchain框架调用本地模型&#xff0c;使得用户可以直接提出问题或发送指令&#xff0c;而无需担心具体的步骤或流程。通过LangChain和chatglm3-6b模型的整合&#xff0c;可以更好地处理对话&#xff0c;提供更智能、更准确的响应&#xff0c;从而提高对话系统的性能…

LoggerFactory is not a Logback

错误信息 LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.SimpleLoggerFactory loaded from file:/D:/maven/repository/org/slf4j/slf4j-simple/1.7.26/slf…

easyx库的学习(鼠标信息)

前言 本次博客是作为介绍easyx库的使用&#xff0c;最好是直接代码打到底&#xff0c;然后看效果即可 代码 int main() {initgraph(640, 480, EX_SHOWCONSOLE|EX_DBLCLKS);setbkcolor(RGB(231, 114, 227));cleardevice();//定义消息结构体ExMessage msg { 0 };//获取消息wh…

SpringBoot+Vue开发记录(三)

说明&#xff1a;本篇文章的主要内容为需求分析。需求分析这一部分很重要&#xff0c;也稍微有点子难搞&#xff0c;所以本篇文章里的有些内容会有失偏颇。 一、准备步骤 我打算做一个刷题项目&#xff0c;但是具体这个项目该怎么做&#xff0c;我是一头雾水。 所以就要先进行…

OPTEE的FTRACE跟踪技术实战

【按语】:对于排除性能问题或优化代码来说,有没有更好的工具可以使用?FTRACE记录了对函数的所有调用,并包含计时信息。因此,对于排除性能问题或优化代码来说,它是一个很有价值的工具。本博客描述如何使用FTRACE为TA生成函数调用图。相关知识点介绍,请参考OPTEE Ftrace函…

k8s calico vxlan式详解

之前的文章讲了k8s ipip模式的使用以及流量路径&#xff0c;本篇文章主要是来讲解一下vxlan 模式下pod 流量是如何通信的。 一、ipip模式转vxlan 修改calico backend参数 将calico_backend参数由bird设置为vxlan,因为vxlan部署不使用bgp 修改calico controllers的configmap…
最新文章