处理粘包问题中服务端接受逻辑修改的解析
void Session::handle_read(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<Session> shared_self)
{
if (!error) {
//已经移动的字符数
int copy_len = 0;
while (bytes_transferred > 0) {
if (!_b_head_parse) {
//收到的数据不足头部大小
if (bytes_transferred + _recv_head_node->_cur_len < HEAD_LENGTH) {
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_head_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
//收到的数据比头部多
//头部剩余未复制的长度
int head_remain = HEAD_LENGTH - _recv_head_node->_cur_len;
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data + copy_len, head_remain);
//更新已处理的data长度和剩余未处理的长度
copy_len += head_remain;
bytes_transferred -= head_remain;
//获取头部数据
short data_len = 0;
memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
cout << "data_len is " << data_len << endl;
//头部长度非法
if (data_len > MAX_LENGTH) {
std::cout << "invalid data length is " << data_len << endl;
_server->clearSession(_uuid);
return;
}
_recv_msg_node = make_shared<MsgNode>(data_len);
//消息的长度小于头部规定的长度,说明数据未收全,则先将部分消息放到接收节点里
if (bytes_transferred < data_len) {
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, shared_self));
//头部处理完成
_b_head_parse = true;
return;
}
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, data_len);
_recv_msg_node->_cur_len += data_len;
copy_len += data_len;
bytes_transferred -= data_len;
_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';
cout << "receive data is " << _recv_msg_node->_data << endl;
//此处可以调用Send发送测试
Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
//继续轮询剩余未处理数据
_b_head_parse = false;
_recv_head_node->Clear();
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
continue;
}
//已经处理完头部,处理上次未接受完的消息数据
//接收的数据仍不足剩余未处理的
int remain_msg = _recv_msg_node->_total_len - _recv_msg_node->_cur_len;
if (bytes_transferred < remain_msg) {
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, remain_msg);
_recv_msg_node->_cur_len += remain_msg;
bytes_transferred -= remain_msg;
copy_len += remain_msg;
_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';
cout << "receive data is " << _recv_msg_node->_data << endl;
//此处可以调用Send发送测试
Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
//继续轮询剩余未处理数据
_b_head_parse = false;
_recv_head_node->Clear();
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
continue;
}
}
else {
std::cout << "handle read failed, error is " << error.what() << endl;
Close();
_server->clearSession(_uuid);
}
}
- 消息节点的数据结构
MsgNode
class MsgNode
{
friend class Session;
public:
MsgNode(char* msg, short max_len) :_total_len(max_len + HEAD_LENGTH), _cur_len(0) {
_data = new char[_total_len + 1]();
memcpy(_data, &max_len, HEAD_LENGTH);
memcpy(_data + HEAD_LENGTH, msg, max_len);
_data[_total_len] = '\0';
}
MsgNode(short max_len) :_total_len(max_len), _cur_len(0) {
_data = new char[_total_len + 1]();
}
~MsgNode() {
delete[] _data;
}
void Clear() {
::memset(_data, 0, _total_len);
_cur_len = 0;
}
private:
short _cur_len;
short _total_len;
char* _data;
};
一、先明确核心成员与依赖
| :成员变量 / 类型 | :作用说明 |
|---|---|
_socket | Boost.Asio 的 tcp::socket 对象,用于异步读写数据 |
_data[MAX_LENGTH] | 单次异步读的临时缓冲区,存储从 socket 读取的原始字节流 |
_b_head_parse | 标记位:false 表示未解析完头部,true 表示已解析头部、正在接收消息体 |
_recv_head_node | 头部节点(智能指针),存储待解析的头部数据,包含:_data:头部缓冲区_cur_len:已接收的头部字节数_total_len:固定为 HEAD_LENGTH |
_recv_msg_node | 消息体节点(智能指针),存储待接收的消息体数据,包含:_data:消息体缓冲区_cur_len:已接收的消息体字节数_total_len:头部指定的消息体长度 |
_server | 服务器类指针,用于管理会话(如 ClearSession 清理会话) |
_uuid | 会话唯一标识,用于服务器定位并清理当前会话 |
MsgNode | 自定义的消息节点类,构造函数接收消息体长度,初始化缓冲区 |
HEAD_LENGTH | 常量,头部长度(示例中是 short 类型,通常为 2) |
MAX_LENGTH | 常量,消息体的最大长度(防止恶意数据导致内存溢出) |
二、函数参数与核心目的
void CSession::HandleRead(
const boost::system::error_code& error, // 异步读操作的错误码(成功时为空)
size_t bytes_transferred, // 异步读实际读取的字节数
std::shared_ptr<CSession> shared_self // 会话的智能指针(防止析构,延长生命周期)
)
- 核心目的:作为
async_read_some的回调函数,处理读取到的字节流,按 “头部 + 消息体” 协议解析数据,解决 TCP 粘包 / 拆包问题。 shared_self的作用:异步操作是非阻塞的,若会话对象被提前析构(如服务器清理时),回调函数会访问野指针。传入shared_ptr可延长对象生命周期,直到回调执行完成。
三、代码执行流程(逐段拆解)
代码整体分为错误处理和正常数据解析两大分支,正常解析又分为头部解析阶段和消息体解析阶段。
1. 错误处理分支(异步读失败)
else {
std::cout << "handle read failed, error is " << error.what() << endl;
Close(); // 关闭 socket 连接
_server->ClearSession(_uuid); // 通知服务器清理当前会话
}
- 当异步读操作出错(如客户端断开连接、网络异常),关闭套接字并让服务器清理会话资源。
2. 正常数据解析分支(无错误)
核心逻辑是循环处理读取到的字节流(因为单次 async_read_some 可能读取到:多个完整消息、一个不完整消息、部分头部 + 部分消息体等情况),直到bytes_transferred(剩余未处理字节数)为 0。
阶段 1:头部解析阶段(_b_head_parse = false)
此时还未解析出完整的头部,需要先接收足够的头部数据。
// 情况1:已接收的头部数据 + 本次读取的字节数 < 头部长度(头部未收全)
if (bytes_transferred + _recv_head_node->_cur_len < HEAD_LENGTH) {
// 将本次读取的所有字节追加到头部缓冲区
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data+ copy_len, bytes_transferred);
_recv_head_node->_cur_len += bytes_transferred;
// 清空临时缓冲区,再次发起异步读,等待剩余头部数据
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
- 若头部数据未收全,将本次读取的字节全部存入头部缓冲区,然后再次发起异步读,等待剩余头部数据。
// 情况2:已接收的头部数据 + 本次读取的字节数 >= 头部长度(头部可解析)
// 计算头部剩余未复制的字节数
int head_remain = HEAD_LENGTH - _recv_head_node->_cur_len;
// 复制剩余头部数据到头部缓冲区,补全头部
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data+copy_len, head_remain);
// 更新已处理的字节数(copy_len)和剩余未处理的字节数(bytes_transferred)
copy_len += head_remain;
bytes_transferred -= head_remain;
// 从头部缓冲区解析出消息体长度(short 类型)
short data_len = 0;
memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
cout << "data_len is " << data_len << endl;
// 校验消息体长度:若超过最大值,视为非法数据,清理会话
if (data_len > MAX_LENGTH) {
std::cout << "invalid data length is " << data_len << endl;
_server->ClearSession(_uuid);
return;
}
// 创建消息体节点,缓冲区大小为解析出的消息体长度
_recv_msg_node = make_shared<MsgNode>(data_len);
- 补全头部数据后,解析出消息体长度,并校验合法性(防止缓冲区溢出)。
// 子情况1:本次剩余字节数 < 消息体长度(消息体未收全)
if (bytes_transferred < data_len) {
// 将本次剩余字节存入消息体缓冲区
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
// 清空临时缓冲区,再次发起异步读,等待剩余消息体数据
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
// 标记头部已解析完成,进入消息体解析阶段
_b_head_parse = true;
return;
}
// 子情况2:本次剩余字节数 >= 消息体长度(消息体可收全)
// 复制完整的消息体数据到消息体缓冲区
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, data_len);
_recv_msg_node->_cur_len += data_len;
// 更新已处理和剩余字节数
copy_len += data_len;
bytes_transferred -= data_len;
// 给消息体末尾加结束符(方便打印,业务层可根据需求调整)
_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);
// 重置状态,准备处理下一个消息的头部
_b_head_parse = false;
_recv_head_node->Clear(); // 清空头部缓冲区,重置_cur_len为0
// 若剩余字节数为0,清空临时缓冲区,再次发起异步读
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
// 若还有剩余字节数,继续循环处理(可能是下一个消息的头部/消息体)
continue;
- 若消息体未收全,存入部分数据后再次发起异步读;若收全,则处理业务并重置状态,继续处理剩余字节。
阶段 2:消息体解析阶段(_b_head_parse = true)
此时头部已解析完成,正在接收消息体的剩余数据。
// 计算消息体剩余未接收的字节数
int remain_msg = _recv_msg_node->_total_len - _recv_msg_node->_cur_len;
// 情况1:本次读取的字节数 < 剩余消息体长度(消息体仍未收全)
if (bytes_transferred < remain_msg) {
// 追加本次字节到消息体缓冲区
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
// 清空缓冲区,再次发起异步读
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
// 情况2:本次读取的字节数 >= 剩余消息体长度(消息体收全)
// 复制剩余消息体数据,补全消息体
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, remain_msg);
_recv_msg_node->_cur_len += remain_msg;
// 更新已处理和剩余字节数
bytes_transferred -= remain_msg;
copy_len += remain_msg;
// 加结束符并打印
_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);
// 重置状态,准备处理下一个消息的头部
_b_head_parse = false;
_recv_head_node->Clear();
// 若剩余字节数为0,清空缓冲区,再次发起异步读
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),
std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self));
return;
}
// 若还有剩余字节数,继续循环处理
continue;
- 逻辑与头部解析阶段的消息体处理类似,核心是补全消息体、处理业务、重置状态。
四、关键设计
- 解决 TCP 粘包 / 拆包:通过 “头部定长 + 消息体变长” 的协议格式,明确消息边界,处理了以下场景:
- 单次读取到不完整的头部 / 消息体(拆包)。
- 单次读取到多个完整消息(粘包)。
- 单次读取到 “部分头部 + 完整消息体 + 下一个消息的头部”(混合场景)。
- 生命周期管理:通过
shared_self(std::shared_ptr<CSession>)绑定到回调函数,防止异步操作过程中会话对象被析构,避免野指针访问。 - 循环处理剩余数据:使用
while循环处理单次读取的字节流,确保所有数据都被解析,不遗漏粘包的消息。
1万+

被折叠的 条评论
为什么被折叠?



