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; }
关键逻辑说明
定时器与异步读的联动:
- 异步读启动后,同时启动20ms定时器。
- 如果异步读在超时前完成,会主动取消定时器,避免不必要的超时操作。
- 如果定时器先触发,会取消当前的异步读操作,此时读回调会收到
operation_aborted错误,但仍会执行,确保你能获取到已经读取的字节数。
确保回调执行:
- 使用
run_one()循环直到operationCompleted为true,保证所有相关回调(读回调或定时器回调)都能被处理,不会丢失数据。 - 取消异步读操作不会关闭套接字,只是终止当前的读请求,连接保持正常,下次可以继续发起读操作。
- 使用
互斥锁的作用:
std::lock_guard确保同一时间只有一个读操作在执行,写线程必须等待读操作完成后才能获取锁,符合你的业务约束。
内容的提问来源于stack exchange,提问作者pSquared
相关产品推荐
相关产品推荐

