C++ 网络编程(10) asio处理粘包的简易方式

🎯 C++ Boost.Asio 处理粘包简易方式快速上手指南

📅 更新时间:2025年6月7日

🏷️ 标签:C++ | Boost.Asio | 网络编程 | 粘包 | TCP

文章目录


前言

之前我们在第8节 介绍处理粘包问题的时候采用 async_read_some 函数监听读事件

这样会导致有消息来,就会立马调用回调函数,导致我们写回调的时候代码冗长,比较复杂

这次我们用 async_read 来一次性把数据接收完


提示:以下是本篇文章正文内容,下面案例可供参考

一、回顾前文

模式流程

之前我们的模式是,有数据来,然后直接进入回调函数,在回调函数中判断
数据头处理

然后再继续
数据信息处理

这样就导致比较麻烦,因为可能数据比较小,又或者比较大,需要多个if去判断

之前我们介绍了通过async_read_some函数监听读事件,并且绑定了读事件的回调函数HandleRead

cpp 复制代码
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), 
std::bind(&CSession::HandleRead, this, 
std::placeholders::_1, std::placeholders::_2, SharedSelf()));

async_read_some 这个函数的特点是只要对端发数据,服务器接收到数据,即使没有收全对端发送的数据也会触发HandleRead函数 ,所以我们会在HandleRead回调函数里判断接收的字节数,接收的数据可能不满足头部长度,可能大于头部长度但小于消息体的长度,可能大于消息体的长度,还可能大于多个消息体的长度,所以要切包等,这些逻辑写起来很复杂,所以我们可以通过读取指定字节数,直到读完这些字节才触发回调函数,那么可以采用async_read函数,这个函数指定读取指定字节数,只有完全读完才会触发回调函数

架构图


这边就不放代码,过于冗长

可以自己看第8节的文章

二、处理粘包简易方式

架构图

单独处理头和数据

我们开辟两个函数,来单独处理头和数据区域

cpp 复制代码
void HandleReadMsg(const boost::system::error_code& ec, 
size_t bytes_transferred, std::shared_ptr<CSession>_self_shared);//处理消息体

void HandReadHead(const boost::system::error_code& ec, 
size_t bytes_transferred, std::shared_ptr<CSession>_self_shared);//处理头部

获取头部数据

我们可以读取指定的头部长度,大小为HEAD_LENGTH字节数,只有读完HEAD_LENGTH字节才触发HandleReadHead函数

cpp 复制代码
void CSession::Start()
{
	_recv_head_node->Clear();
	boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH),
	
		std::bind(&CSession::HandReadHead, this, std::placeholders::_1, 
		std::placeholders::_2, shared_from_this()));

}

这样我们可以直接在HandleReadHead函数内处理头部信息

单独处理头

cpp 复制代码
//处理头部
void CSession::HandReadHead(const boost::system::error_code& ec,
 size_t bytes_transferred, std::shared_ptr<CSession>_self_shared)
{
	if (!ec)
	{
		if (bytes_transferred < HEAD_LENGTH)//这种情况基本上不可能发生 不过也写上
		{
			cout << "read head length error" << endl;
			Close();
			_server->ClearSession(_uuid); 
			return;
		}


		//头部收完了
		//获取数据长度
		short data_len = 0;
		memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
		data_len = boost::asio::detail::socket_ops::network_to_host_short(data_len);//转换为本地字节序
		cout << "data len is" << data_len << endl;

		if (data_len > max_length)
		{
			cout << "invalid data length is" << data_len << endl;
			_server->ClearSession(_uuid);
			return;
		}

		//准备监听数据区域
		_recv_msg_node = make_shared<MsgNode>(data_len);
		boost::asio::async_read(_socket, boost::asio::buffer(_recv_msg_node->_data, 
		_recv_msg_node->_total_len),
		
			std::bind(&CSession::HandleReadMsg, this, 
			std::placeholders::_1, std::placeholders::_2, shared_from_this()));
	}
	else
	{
		cout << "handle read head failed    " << endl;
		Close();
		_server->ClearSession(_uuid);
		return;
	}
}

单独处理数据区域

HandleReadMsg函数内解析消息体,解析完成后打印收到的消息,接下来继续监听读事件,监听读取指定头部大小字节,触发HandleReadHead函数, 然后再在HandleReadHead内继续监听读事件,获取消息体长度数据后触发HandleReadMsg函数,从而达到循环监听的目的

cpp 复制代码
//处理消息体
void  CSession::HandleReadMsg(const boost::system::error_code& ec, 
size_t bytes_transferred, std::shared_ptr<CSession>_self_shared)
{
	if (!ec)
	{
		std::chrono::milliseconds dura(2000);//为了演示
		std::this_thread::sleep_for(dura);
		_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';
		cout << "receive data is " << _recv_msg_node->_data << endl;
		Send(_recv_msg_node->_data, _recv_msg_node->_total_len);//发回回去
		//再次接收头部数据
		_recv_head_node->Clear();
		boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH),
			std::bind(&CSession::HandReadHead, this, std::placeholders::_1, std::placeholders::_2,
				shared_from_this()));
	}
	else
	{
		cout << "handle read head failed    " << endl;
		Close();
		_server->ClearSession(_uuid);
		return;
	}
}

总结

本文介绍了如何使用async_read函数,监听读事件获取指定字节数才触发回调函数,用这种办法处理粘包问题很简单

❤️ 如果你觉得本文对你有帮助,欢迎点赞、评论与收藏。更多 c++ asio网络编程 开发知识,敬请关注后续更新!

相关推荐
二哈赛车手6 小时前
新人笔记---ApiFox的一些常见使用出错
java·笔记·spring
栗子~~6 小时前
JAVA - 二层缓存设计(本地缓冲+redis缓冲+广播所有本地缓冲失效) demo
java·redis·缓存
星寂樱易李6 小时前
iperf3 + Python-- 网络带宽、网速、网络稳定性
开发语言·网络·python
YDS8296 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— RAG知识库的搭建和接口实现
java·ai·springboot·agent·rag·deepseek
未若君雅裁8 小时前
MyBatis 一级缓存、二级缓存与清理机制
java·缓存·mybatis
于小猿Sup8 小时前
VMware在Ubuntu22.04驱动Livox Mid360s
linux·c++·嵌入式硬件·自动驾驶
AI人工智能+电脑小能手8 小时前
【大白话说Java面试题 第65题】【JVM篇】第25题:谈谈对 OOM 的认识
java·开发语言·jvm
阿维的博客日记9 小时前
Nacos 为什么能让配置动态生效?(涉及 @RefreshScope 注解)
java·spring
雨辰AI9 小时前
SpringBoot3 + 人大金仓读写分离 + 分库分表 + 集群高可用 全栈实战
java·数据库·mysql·政务
随身数智备忘录10 小时前
什么是设备管理体系?设备管理体系包含哪些核心模块?
网络·数据库·人工智能