You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++17非Boost ASIO带超时读取问题:数据丢失与优雅停止

问题分析

你的核心问题出在直接停止io_context的操作上:当调用m_TCPioContext.stop()时,ASIO会立即终止所有未完成的异步操作,这些操作的回调函数不会被执行。这就导致超时后,即使异步读已经读取了部分数据,你也无法通过回调获取到这些数据,最终出现数据丢失。后续的run()调用因为没有待处理的工作(操作已被取消),自然也不会触发任何回调。

另外,你的需求是在20ms窗口内尽可能读取数据、保持套接字连接、不额外占用线程,这需要用定时器配合异步操作取消的方式实现,而不是粗暴停止整个io_context。

解决方案

下面是修正后的实现,通过asio::steady_timer触发超时,取消当前的异步读操作,同时确保回调函数总能执行,从而获取所有已读取的数据:

首先,在你的类中添加一个定时器成员:

asio::steady_timer m_TCPTimeoutTimer;

在类的构造函数中初始化它(和io_context关联):

YourClass::YourClass(asio::io_context& ioContext)
    : m_TCPioContext(ioContext),
      m_TCPTimeoutTimer(ioContext)
{
    // 其他初始化逻辑
}

然后替换原有的Read和RunIOContextWithTimeOut函数:

ErrorCode Read(uint8_t* buf, unsigned int maxAmountOfBytesToRead, unsigned int& nRead)
{
    std::lock_guard<std::mutex> tcpSocketLock(m_TCPSocketMutex);

    asio::error_code asioError;
    unsigned int amountOfBytesInBuffer = 0;
    bool operationCompleted = false;

    // 异步读回调:完成后取消定时器,记录结果
    auto readCallback = [&](const asio::error_code& errorCode, unsigned int bytesRead) {
        m_TCPTimeoutTimer.cancel(); // 定时器不再需要,取消它
        asioError = errorCode;
        amountOfBytesInBuffer = bytesRead;
        operationCompleted = true;
    };

    // 发起异步读操作
    m_TCPConnectionSocket.async_read_some(
        asio::buffer(buf, maxAmountOfBytesToRead),
        readCallback
    );

    // 超时定时器回调:超时则取消异步读
    auto timerCallback = [&](const asio::error_code& errorCode) {
        if (!errorCode) { // 定时器未被取消,说明触发了超时
            m_TCPConnectionSocket.cancel(); // 取消当前的异步读操作
        }
    };

    // 设置定时器并启动
    m_TCPTimeoutTimer.expires_after(std::chrono::milliseconds(20));
    m_TCPTimeoutTimer.async_wait(timerCallback);

    // 运行io_context,直到读操作完成(不管是成功、失败还是超时取消)
    m_TCPioContext.restart();
    while (!operationCompleted) {
        m_TCPioContext.run_one(); // 逐个处理事件,直到读回调执行完毕
    }
    m_TCPioContext.stop(); // 清理剩余的未处理工作

    nRead = amountOfBytesInBuffer;

    // 处理取消错误:超时导致的取消不算IO错误,返回成功(已读取的数据有效)
    if (asioError == asio::error::operation_aborted) {
        return ErrorCode::Success;
    }

    // 其他错误返回IO错误,无错误则返回成功
    return asioError ? ErrorCode::IOError : ErrorCode::Success;
}
关键逻辑说明
  1. 定时器与异步读的联动:

    • 异步读启动后,同时启动20ms定时器。
    • 如果异步读在超时前完成,会主动取消定时器,避免不必要的超时操作。
    • 如果定时器先触发,会取消当前的异步读操作,此时读回调会收到operation_aborted错误,但仍会执行,确保你能获取到已经读取的字节数。
  2. 确保回调执行:

    • 使用run_one()循环直到operationCompleted为true,保证所有相关回调(读回调或定时器回调)都能被处理,不会丢失数据。
    • 取消异步读操作不会关闭套接字,只是终止当前的读请求,连接保持正常,下次可以继续发起读操作。
  3. 互斥锁的作用:

    • std::lock_guard确保同一时间只有一个读操作在执行,写线程必须等待读操作完成后才能获取锁,符合你的业务约束。

内容的提问来源于stack exchange,提问作者pSquared

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 08:30:55