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

Boost beast::websocket异步客户端示例改造:实现长连接重复收发

我懂你想把Boost Beast那个单次收发的异步WebSocket客户端改成能持续保持会话、反复发收消息的版本——原示例握手完就发固定字符串、收完回复直接退出,确实没法复用连接。下面给你捋捋具体怎么改:

核心思路

原示例的session类是一次性的,完成一轮收发后就自动销毁了。我们要做的就是:

  • 让会话类保持存活,直到主动关闭连接
  • 支持随时发送消息(比如从用户输入读取)
  • 循环处理接收,一直接收服务器发来的消息
具体修改步骤

1. 给会话类加消息队列和状态标记

首先在session类里加两个成员变量,用来管理待发送的消息和当前是否正在写操作:

class session : public std::enable_shared_from_this<session>
{
    // ... 原有成员 ...
    std::queue<std::string> messages_; // 待发送消息队列
    bool is_writing_ = false; // 标记当前是否正在执行异步写
    beast::flat_buffer buffer_; // 用来存接收的消息,原示例里有,保留它
public:
    // ... 原有方法 ...
    void send(std::string msg); // 外部调用的发送接口
private:
    void do_send(std::string msg); // 内部实际处理发送的方法
    void do_write(); // 执行异步写的方法
    // ... 其他原有方法 ...
};

2. 实现安全的发送接口

因为Beast的异步操作必须在io_context的strand里执行,所以我们用net::post把发送操作投递到strand上,避免线程安全问题:

void session::send(std::string msg)
{
    // 把发送请求投递到WebSocket的执行器(strand)上
    net::post(ws_.get_executor(),
        std::bind(&session::do_send, shared_from_this(), std::move(msg)));
}

void session::do_send(std::string msg)
{
    bool was_empty = messages_.empty();
    messages_.push(std::move(msg));
    // 如果之前队列是空的,且当前没有在写,就启动写操作
    if (was_empty && !is_writing_)
    {
        do_write();
    }
}

void session::do_write()
{
    is_writing_ = true;
    // 取队列第一个消息发送
    ws_.async_write(
        net::buffer(messages_.front()),
        beast::bind_front_handler(
            &session::on_write,
            shared_from_this()));
}

3. 修改写完成后的逻辑,支持连续发送

原示例的on_write之后就准备关闭连接了,现在改成发送完一个消息后,如果队列还有消息就继续发:

void session::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
    boost::ignore_unused(bytes_transferred);

    if(ec)
        return fail(ec, "write");

    messages_.pop();
    is_writing_ = false;

    // 队列还有消息的话,继续发送下一条
    if (!messages_.empty())
    {
        do_write();
    }
}

4. 修改握手和接收逻辑,保持会话活跃

原示例握手后直接发固定消息,现在改成握手成功后启动循环接收,同时可以加一个线程处理用户输入:

void session::on_handshake(beast::error_code ec)
{
    if(ec)
        return fail(ec, "handshake");

    std::cout << "✅ 连接成功!可以输入消息发送了(Ctrl+D退出)\n";

    // 启动异步读取,一直接收服务器的消息
    do_read();

    // 新开一个线程处理用户输入,避免阻塞io_context
    std::thread([self = shared_from_this()]() {
        std::string line;
        while (std::getline(std::cin, line))
        {
            self->send(line);
        }
        // 用户输入结束,发送关闭帧
        net::post(self->ws_.get_executor(),
            std::bind(&session::do_close, self));
    }).detach();
}

// 补充一个主动关闭的方法
void session::do_close()
{
    ws_.async_close(websocket::close_code::normal,
        beast::bind_front_handler(
            &session::on_close,
            shared_from_this()));
}

void session::on_close(beast::error_code ec)
{
    if(ec)
        return fail(ec, "close");

    std::cout << "🔌 连接已关闭\n";
}

5. 修改接收逻辑,循环读取

原示例on_read之后就关闭连接,现在改成读完一条消息后,立即启动下一次读取:

void session::do_read()
{
    ws_.async_read(
        buffer_,
        beast::bind_front_handler(
            &session::on_read,
            shared_from_this()));
}

void session::on_read(beast::error_code ec, std::size_t bytes_transferred)
{
    boost::ignore_unused(bytes_transferred);

    if(ec)
    {
        // 如果是正常关闭,就不报错
        if(ec != websocket::error::closed)
            fail(ec, "read");
        return;
    }

    // 打印收到的消息
    std::cout << "📥 收到:" << beast::buffers_to_string(buffer_.data()) << "\n";
    buffer_.consume(buffer_.size()); // 清空缓冲区,准备下一次接收

    // 继续等待下一条消息
    do_read();
}
关键注意点
  • 线程安全:所有操作WebSocket的代码都要在strand里执行,所以用net::post把外部线程的发送请求投递到strand上
  • 会话生命周期:通过shared_from_this()保持会话的引用,避免在异步操作完成前被销毁
  • 优雅关闭:用户输入结束时主动发送关闭帧,而不是直接断开连接

这样改完之后,客户端就能保持连接,随时发送消息,一直接收服务器的回复,直到用户主动退出或者连接断开。

内容的提问来源于stack exchange,提问作者Blair Fonville

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:35:18