Asio12-HandlePacketStickingProblemSimply

Asio12-HandlePacketStickingProblemSimply

在Asio8中我们处理过粘包的问题,下面的部分的源码:

void CSession::HandleRead(const boost::system::error_code& error, size_t bytes_transferred, shared_ptr<CSession> _self_shared)
{
    if (!error) {
        PrintRecvData(_data, bytes_transferred);
        std::chrono::milliseconds dura(2000);
        std::this_thread::sleep_for(dura);
        // 已经移动的字节数:在消息体中
        int copy_len = 0;
        while (bytes_transferred > 0) {
            // 如果还未解析头部
            if (!_head_parsed) {
                // 确保头部信息体+待处理的也不会溢出:收到的比头部小
                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, _self_shared));
                    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);
                copy_len += head_remain;
                bytes_transferred -= head_remain;
                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);
                // data_len = ntohs(data_len);
                if (data_len > MAX_LENGTH) {
                    std::cout << "data_len" << data_len << std::endl;
                    std::cout << "Data length is too long" << std::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);
                    // 头部处理完成
                    _head_parsed = true;
                    _socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
                    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';
                std::cout << "Received message: " << _recv_msg_node->_data << std::endl;
                Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
                _head_parsed = 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(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
                    return;
                }
                continue;
            }
            std::cout << "--------------------" << std::endl;
            // 已经处理完头部,继续上次未处理完的消息
            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, _self_shared));
                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 - 1] = '\0';
            std::cout << "Received message2: " << _recv_msg_node->_data << std::endl;
            Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
            _head_parsed = 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(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
                return;
            }
            continue;
        }
    } else {
        std::cout << "handle read failed, error is " << error.what() << endl;
        _server->ClearSession(_uuid);
    }
}

很明显,将所有不同的情况全部放置在一个函数处理,既复杂,同时也容易出错。原因在于我们异步读的时候使用的是async_read_some,只要接收到信息,asio框架就会触发回调,而这时候就有可能读取到支离破碎(不整体规整)的内容。

因此我们改进我们的做法,我们异步读取的时候使用的是async_read.区别在于这个读的内部实际会多次调用async_read_some,但是只有读取到指定的字节数的时候,才会触发一次回调。这样既解决了多次回调的效率问题,也解决了粘包的问题。

我们的思路是,开始,我们先读取HEAD_LENGTH的字节,用于填充_recv_head_node,解析消息体的长度,然后我们根据这个长度,async_read去接受消息体的内容。读取到指定的字节数之后,触发回调,填充_recv_msg_node,这时候内部的_data就是消息体的内容了。

接下来是代码:

// 这时候我们改动Start函数,开始先要读取两个字节用于解析头
void CSession::Start()
{
    boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH), std::bind(&CSession::HandleHeader, this, std::placeholders::_1, std::placeholders::_2, SharedSelf()));
}
// HandleHead : 解析头部
void CSession::HandleHeader(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self)
{
    if (error) {
        if (error == boost::asio::error::eof) {
            std::cout << "Connection closed by peer" << std::endl;
        } else {
            std::cout << "Error reading header: " << error.message()
                      << ", bytes transferred: " << bytes_transferred << std::endl;
        }
        _server->ClearSession(_uuid);
        return;
    }

    short data_len = 0;
    memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);

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

    std::cout << "data_len:" << data_len;
    _recv_head_node->Clear();

    _recv_msg_node = make_shared<MsgNode>(data_len);
    boost::asio::async_read(_socket, boost::asio::buffer(_recv_msg_node->_data, data_len), std::bind(&CSession::HandleMsg, this, std::placeholders::_1, std::placeholders::_2, shared_self));
}

// HandleMsg:用于存储消息体
void CSession::HandleMsg(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self)
{
    if (!error) {
        std::cout << "=" << bytes_transferred << std::endl;
        PrintRecvData(_recv_msg_node->_data, bytes_transferred);
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        _recv_msg_node->_data[bytes_transferred] = '\0';
        std::cout << "Received message: " << _recv_msg_node->_data << std::endl;
        for (int i = 0; i < bytes_transferred; i++) {
            _recv_msg_node->_data[i] = toupper(_recv_msg_node->_data[i]);
        }

        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::HandleHeader, this, std::placeholders::_1, std::placeholders::_2, shared_self));
    }
}

image-20250922171235517

posted @ 2025-12-24 23:13  大胖熊哈  阅读(12)  评论(0)    收藏  举报