如何正确断开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中注销并删除,额外的回调和读取套接字来丢弃数据显得冗余。
请问:
- 这是关闭Beast WebSocket的正确方式吗?
- 如果我不在意后续数据,甚至可以接受非优雅断开连接,是否有更好的方式直接终止连接并删除流,而不会在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:非优雅关闭的直接实现
如果不需要优雅关闭流程,可以直接强制终止连接,步骤如下:
- 取消所有异步操作:调用
_stream.get_executor().cancel(),会取消所有关联到该执行器的异步操作,回调会收到asio::error::operation_aborted错误。 - 关闭底层套接字:由于你的流是
WsStream<SslStream<TcpStream>>结构,需要通过两层next_layer()拿到底层TCP套接字,直接关闭强制断开连接。 - 安全销毁对象:因为是单线程运行,取消操作后所有未完成的回调会在当前线程的
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

