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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 05:30:04