使用boost::asio::use_future调用async_read时,如何获取EOF时的传输字节数?
问题背景
为适配第三方库的阻塞式调用需求,我们基于Boost.Asio实现网络通信:用少量网络线程处理异步操作,第三方库通过专用线程调用Session::read方法。当前使用asio::async_read结合use_future实现异步转阻塞,但遇到对等端断开连接(触发boost::asio::error::eof)时,future会直接抛出异常,无法获取断开前已传输的字节数;而使用回调handler的方式,能同时拿到错误码和已传输字节数。
示例代码:
namespace ba = boost::asio; int32_t Session::read (unsigned char* pBuffer, uint32_t bufferSizeToRead) { // 创建可变缓冲区 ba::mutable_buffer buffer (pBuffer, bufferSizeToRead); int32_t result = 0; // 发起异步读操作,通过future让调用线程阻塞等待结果 std::future<std::size_t> future = ba::async_read(m_socket, buffer, ba::bind_executor(m_sessionStrand, ba::use_future)); try { // 阻塞调用线程,直到异步读操作完成 result = future.get(); } catch (boost::system::system_error &ex) { auto exitCode = ex.code().value(); if ( exitCode == ba::error::eof ) { log ("Connection closed by the peer"); } } return result; // 发生EOF时此处返回0 }
存在两个疑问:
- 使用future时,是否仍能获取已传输的字节数?
- 是否有其他方案,既能提供阻塞式调用,又能使用
asio::async_read或类似异步接口?
解答
1. Future模式下能否获取EOF时的已传输字节数?
直接使用ba::use_future无法实现。因为use_future的设计逻辑是:操作成功时将已传输字节数存入future;操作出错(包括EOF)时则抛出包含错误码的异常,不会携带任何部分结果。future本身只能传递单一的成功值或异常,无法同时返回错误状态和已传输字节数,这是该模式的固有局限性。
2. 替代方案:自定义Promise包装器
要同时满足阻塞式调用和获取EOF时的部分结果,可以自定义一个包含错误码和字节数的结果结构体,通过std::promise将两者绑定后传递给future,这样调用线程阻塞等待时能拿到完整的结果信息。
示例实现:
#include <boost/asio.hpp> #include <future> namespace ba = boost::asio; // 自定义结果结构体,同时保存错误码和已传输字节数 struct ReadResult { boost::system::error_code ec; std::size_t bytes_transferred; }; int32_t Session::read(unsigned char* pBuffer, uint32_t bufferSizeToRead) { ba::mutable_buffer buffer(pBuffer, bufferSizeToRead); std::promise<ReadResult> read_promise; auto read_future = read_promise.get_future(); // 发起异步读操作,用自定义handler打包结果到promise ba::async_read(m_socket, buffer, ba::bind_executor(m_sessionStrand, [promise = std::move(read_promise)](const boost::system::error_code& ec, std::size_t bytes) mutable { promise.set_value({ec, bytes}); })); // 阻塞等待异步操作完成,获取完整结果 auto result = read_future.get(); if (result.ec) { if (result.ec == ba::error::eof) { log("Connection closed by the peer"); } // 可根据需求处理其他错误类型 } // 返回已传输字节数,即使发生EOF也能拿到有效数据 return static_cast<int32_t>(result.bytes_transferred); }
这个方案的核心是绕过use_future的限制,手动用std::promise传递包含错误和结果的复合结构体,既保留了异步操作的线程池资源复用特性,又能让调用线程以阻塞方式获取完整结果。
另外,也可以直接使用asio::read同步接口,但如果需要复用异步线程池的资源,自定义promise的方式更合适——它本质还是异步执行,只是调用线程阻塞等待,不会占用线程池中的工作线程。
内容的提问来源于stack exchange,提问作者sullyt

