Boost Asio async_read_some使用失败及TCP整包读取方案咨询
关于Boost Asio异步读取非格式化数据及可变长TCP数据包的问题解决
问题根源
你之前用async_read_until基于\n读取数据的方案仅适用于文本格式化数据,改用async_read_some后代码无响应、回调不触发,核心问题出在Defer包装的Socket与回调函数不兼容:
- 你定义的
Socket是deferred_t::as_default_on_t<tcp::socket>,这类对象的异步操作默认返回awaitable,而非接受传统回调函数。直接传入handler不会触发操作执行,反而会导致未定义行为。 - 循环中反复调用
async_read_some却不等待操作完成,会无限提交未执行的异步任务,同时阻塞线程。
修复后的async_read_some异步读取代码
修正代码,利用协程的co_await等待异步操作完成,无需手动传入回调:
#include <boost/asio.hpp> #include <iostream> namespace asio = boost::asio; namespace this_coro = asio::this_coro; using asio::ip::tcp; template <typename T> using Defer = asio::deferred_t::as_default_on_t<T>; using Socket = Defer<tcp::socket>; using Acceptor = Defer<tcp::acceptor>; asio::awaitable<void> session(Socket socket) { auto ep = socket.remote_endpoint(); std::cout << "New session for " << ep << "\n"; size_t requests_handled = 0; try { char data[32]; for (;; ++requests_handled) { // 用co_await等待异步读取完成,获取读取字节数 std::size_t bytes_read = co_await socket.async_read_some(asio::buffer(data, sizeof(data))); std::cout << "Read " << bytes_read << " bytes: " << std::string(data, bytes_read) << "\n"; } } catch (const boost::system::system_error& e) { std::cout << "Session " << ep << " closed: " << e.what() << "\n"; } std::cout << " -- Session " << ep << " handled " << requests_handled << " requests" << std::endl; } asio::awaitable<void> listener(uint16_t port) { auto ex = co_await this_coro::executor; Acceptor acc(ex, { {}, port }); for (;;) { co_spawn(ex, session(co_await acc.async_accept()), asio::detached); } } int main() { asio::io_context ioc(1); co_spawn(ioc, listener(8989), asio::detached); ioc.run(); }
读取可变长完整TCP数据包的实现方案
TCP是流协议,没有天然的数据包边界,要读取完整可变长数据包,最可靠的方式是先读取固定长度的头部(包含数据包总长度),再读取对应长度的正文:
示例代码(基于头部长度的完整数据包读取)
#include <boost/asio.hpp> #include <iostream> #include <vector> namespace asio = boost::asio; namespace this_coro = asio::this_coro; using asio::ip::tcp; template <typename T> using Defer = asio::deferred_t::as_default_on_t<T>; using Socket = Defer<tcp::socket>; using Acceptor = Defer<tcp::acceptor>; // 读取固定长度的数据,处理TCP流分段情况 asio::awaitable<std::size_t> read_exact(Socket& sock, asio::mutable_buffer buf) { std::size_t total_read = 0; while (total_read < asio::buffer_size(buf)) { std::size_t bytes_read = co_await sock.async_read_some( asio::buffer(buf + total_read, asio::buffer_size(buf) - total_read) ); total_read += bytes_read; } co_return total_read; } asio::awaitable<void> session(Socket socket) { auto ep = socket.remote_endpoint(); std::cout << "New session for " << ep << "\n"; try { while (true) { // 1. 读取4字节的头部(存储数据包长度,网络字节序) uint32_t payload_len; co_await read_exact(socket, asio::buffer(&payload_len, sizeof(payload_len))); payload_len = ntohl(payload_len); // 转换为主机字节序 // 2. 根据长度读取完整数据包内容 std::vector<char> payload(payload_len); co_await read_exact(socket, asio::buffer(payload)); std::cout << "Received complete packet (" << payload_len << " bytes): " << std::string(payload.begin(), payload.end()) << "\n"; } } catch (const boost::system::system_error& e) { std::cout << "Session " << ep << " closed: " << e.what() << "\n"; } } asio::awaitable<void> listener(uint16_t port) { auto ex = co_await this_coro::executor; Acceptor acc(ex, { {}, port }); for (;;) { co_spawn(ex, session(co_await acc.async_accept()), asio::detached); } } int main() { asio::io_context ioc(1); co_spawn(ioc, listener(8989), asio::detached); ioc.run(); }
说明
read_exact函数确保读取指定长度的数据,处理TCP流中数据分段的情况。- 头部长度可根据协议调整(如2字节、8字节),注意字节序转换(网络字节序转主机字节序)。
- 如果协议没有头部,需自定义边界标记,但这种方式可靠性较低,仅适用于特定场景。
内容的提问来源于stack exchange,提问作者Jelal
相关产品推荐
相关产品推荐

