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

如何正确断开boost::beast WebSocket流并释放相关资源?

关于Boost.Beast WebSocket客户端安全关闭的问题

我封装了一个基于boost::beast::websocket::stream的WsClient类:

class WsClient
{
    using TcpStream = boost::beast::tcp_stream;
    using SslStream = boost::beast::ssl_stream<TcpStream>;
    using WsStream = boost::beast::websocket::stream<SslStream>;

    ...

    WsStream _stream;
};

该类以单线程方式使用(所有与WebSocket客户端的交互都在asio::io_context运行的同一线程上下文中完成)。我希望安全且彻底地关闭WebSocket客户端,禁用/断开所有异步操作,并释放相关资源。

在Boost.Beast的WebSocket文档中提到,调用async_close后需要持续读取,直到返回error::closed错误,才算连接成功关闭。于是我写了以下代码:先发起async_close,在完成处理程序中重复调用async_read直到收到error::closed:

void WsClient::close()
{
    auto self = shared_from_this();

    _stream.async_close(ws::close_code::normal,
                        [self](boost::system::error_code ec)
                        {
                            self->drainSocket({}, 0);
                        });
}

void WsClient::drainSocket(boost::system::error_code ec, std::size_t)
{
     if (ec != boost::beast::websocket::error::closed)
          _stream.async_read(_read_buf, std::bind_front(&WsClient::drainSocket, shared_from_this()));

     // else 
     //     完成,回调结束,shared_ptr离开作用域,资源被释放
}

这种方式感觉有些繁琐,我不认为这是关闭WebSocket连接、丢弃后续数据并释放资源的最佳方式。理想状态下,我希望只需发起关闭操作,就能把流对象从asio::io_context中注销并删除,额外的回调和读取套接字来丢弃数据显得冗余。

请问:

  1. 这是关闭Beast WebSocket的正确方式吗?
  2. 如果我不在意后续数据,甚至可以接受非优雅断开连接,是否有更好的方式直接终止连接并删除流,而不会在Beast/Asio内部造成悬空引用?

解答

问题1:你的实现符合规范,但可优化

根据Boost.Beast的WebSocket协议要求,发起关闭帧后必须继续读取直到收到对方的关闭响应(即error::closed),这是优雅关闭的标准流程——WebSocket是双向协议,关闭需要双方握手:你发送关闭帧,对方回发关闭帧,之后连接才算正式终止。

你的代码逻辑是正确的,但可以简化实现,不需要单独拆分drainSocket函数,同时建议处理读取过程中的其他异常错误,避免无意义的循环:

void WsClient::close()
{
    auto self = shared_from_this();
    _stream.async_close(ws::close_code::normal,
        [self](boost::system::error_code ec) {
            auto read_loop = [self](boost::system::error_code ec, std::size_t) {
                // 遇到非closed的错误直接终止循环
                if (ec && ec != boost::beast::websocket::error::closed) {
                    return;
                }
                if (ec != boost::beast::websocket::error::closed) {
                    self->_stream.async_read(self->_read_buf, std::move(read_loop));
                }
                // 收到closed错误后,回调结束,shared_ptr引用计数降低,资源自动释放
            };
            read_loop({}, 0);
        });
}

问题2:非优雅关闭的直接实现

如果不需要优雅关闭流程,可以直接强制终止连接,步骤如下:

  1. 取消所有异步操作:调用_stream.get_executor().cancel(),会取消所有关联到该执行器的异步操作,回调会收到asio::error::operation_aborted错误。
  2. 关闭底层套接字:由于你的流是WsStream<SslStream<TcpStream>>结构,需要通过两层next_layer()拿到底层TCP套接字,直接关闭强制断开连接。
  3. 安全销毁对象:因为是单线程运行,取消操作后所有未完成的回调会在当前线程的io_context.run()中执行完毕,只要回调中检测到operation_aborted错误后不再发起新操作,就不会出现悬空引用,之后可安全删除WsClient实例。

示例代码:

void WsClient::force_close()
{
    boost::system::error_code ec;
    // 取消所有异步操作
    _stream.get_executor().cancel(ec);
    // 关闭底层TCP套接字
    _stream.next_layer().next_layer().close(ec);
    // 所有异步回调会收到operation_aborted错误,执行完毕后对象自动销毁
}

这种方式不会发送WebSocket关闭帧,对方会收到连接异常断开的通知,适合对关闭流程无优雅要求的场景。


内容的提问来源于stack exchange,提问作者Steve Lorimer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 10:36:33