处理粘包问题中服务端接受逻辑修改的解析

处理粘包问题中服务端接受逻辑修改的解析

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;
};

一、先明确核心成员与依赖

:成员变量 / 类型:作用说明
_socketBoost.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;
  • 逻辑与头部解析阶段的消息体处理类似,核心是补全消息体、处理业务、重置状态。

四、关键设计

  1. 解决 TCP 粘包 / 拆包:通过 “头部定长 + 消息体变长” 的协议格式,明确消息边界,处理了以下场景:
    • 单次读取到不完整的头部 / 消息体(拆包)。
    • 单次读取到多个完整消息(粘包)。
    • 单次读取到 “部分头部 + 完整消息体 + 下一个消息的头部”(混合场景)。
  2. 生命周期管理:通过shared_selfstd::shared_ptr<CSession>)绑定到回调函数,防止异步操作过程中会话对象被析构,避免野指针访问。
  3. 循环处理剩余数据:使用while循环处理单次读取的字节流,确保所有数据都被解析,不遗漏粘包的消息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值