如何使用boost::spawn实现boost asio异步TCP echo服务器?
问题排查与实现方案
现有代码核心问题
- 无限递归调用:
do_read_init调用do_read后直接递归调用自身,没有退出条件,会直接触发栈溢出,这是第二次读取失败的核心原因 - 未处理异步错误:默认使用
yield_context时如果异步操作返回错误,会直接抛出异常,代码中没有捕获逻辑,一旦遇到连接断开、读写出错就会直接终止 - 缺少echo逻辑:读取到数据后没有写回客户端,不符合echo服务器的基本要求
- 生命周期管理隐患:lambda捕获
this而没有持有shared_from_this(),极端情况可能出现session对象已经析构但协程还在运行的悬垂指针问题
需求实现
1. 非静态成员函数直接传入spawn(无需lambda)
可以通过std::bind绑定成员函数、对象实例的shared_ptr,以及yield_context占位符,直接传给spawn,完全不需要额外写lambda:
void session::start() { // 绑定成员函数与实例,直接传给spawn,无需lambda boost::asio::spawn(socket_.get_executor(), std::bind(&session::do_session, shared_from_this(), std::placeholders::_1)); }
2. 协程语境下的逻辑编写与错误处理
用boost::spawn写异步逻辑不需要手动挂载回调,yield_context已经帮你封装了回调逻辑,可以像写同步代码一样写异步流程,要获取错误码只需要给yield传递error_code引用即可:
void do_session(boost::asio::yield_context yield) { char data_[1024]; boost::system::error_code ec; // 协程内直接写循环,不需要递归调用 while(true) { // 读数据,传yield[ec]就不会抛异常,错误码存在ec里 std::size_t n = socket_.async_read_some(boost::asio::buffer(data_), yield[ec]); if(ec) break; // 出错/连接断开直接退出循环 // 写回echo数据 boost::asio::async_write(socket_, boost::asio::buffer(data_, n), yield[ec]); if(ec) break; } // 循环退出后session生命周期自动管理,不需要额外清理 }
完整可运行代码
#include <cstdlib> #include <iostream> #include <memory> #include <utility> #include <functional> #include <boost/asio.hpp> #include <boost/asio/spawn.hpp> using boost::asio::ip::tcp; using executor_type = boost::asio::any_io_executor; class session : public std::enable_shared_from_this<session> { public: session(tcp::socket socket) : socket_(std::move(socket)) { } void start() { // 直接绑定成员函数,无需lambda boost::asio::spawn(socket_.get_executor(), std::bind(&session::do_session, shared_from_this(), std::placeholders::_1)); } private: void do_session(boost::asio::yield_context yield) { enum { max_length = 1024 }; char data_[max_length]; boost::system::error_code ec; while (true) { // 读取数据,获取错误码 std::size_t n = socket_.async_read_some(boost::asio::buffer(data_, max_length), yield[ec]); if (ec) { // 可在此添加自定义错误处理逻辑 break; } // 写回echo数据 boost::asio::async_write(socket_, boost::asio::buffer(data_, n), yield[ec]); if (ec) { break; } } // 退出时自动清理socket,session对象随shared_ptr引用计数归零自动析构 } tcp::socket socket_; }; class server: public std::enable_shared_from_this<server> { public: server( executor_type exe, short port) : acceptor_(exe, tcp::endpoint(tcp::v4(), port)) { } void start() { do_accept(); } void stop() { acceptor_.close(); } private: void do_accept() { acceptor_.async_accept( [this, self = shared_from_this()] // (boost::system::error_code ec, tcp::socket socket) { if (!ec) { std::make_shared<session>(std::move(socket))->start(); do_accept(); } }); } tcp::acceptor acceptor_; }; int main() { try { boost::asio::io_context io_context; // 建议用1024以上的端口避免权限问题 auto s = std::make_shared<server>(io_context.get_executor(), 2121); s->start(); io_context.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }
补充说明
- 协程语境下用循环代替递归调用,既符合直觉也不会出现栈溢出问题
- 所有异步操作的错误都可以通过
yield[ec]的方式获取,自定义错误逻辑直接判断ec的值即可 - 绑定成员函数时传入
shared_from_this(),保证协程运行期间session对象不会提前析构,避免悬垂指针问题
内容的提问来源于stack exchange,提问作者Vlad Sahar
相关产品推荐
相关产品推荐

