Boost Beast WebSocket客户端连接异常排查求助
问题原因分析
1. Strand资源未重置
WebSocket流对象ws_在构造时绑定了strand,但重连时复用了原对象,旧strand可能因之前的错误处于停止状态,导致后续操作被直接取消。
2. 同步阻塞导致IO上下文停滞
reconnect()方法中使用std::this_thread::sleep_for同步阻塞线程,而io_context可能运行在同一个线程上,阻塞期间事件循环无法处理任何事件,触发系统级的操作取消错误。
3. WebSocket流状态残留
重连时未重置WebSocket流对象的内部状态,之前连接失败的错误状态残留,导致新的连接操作直接触发旧错误。
4. 缺少心跳维持连接
服务器可能因长时间无数据交互主动断开连接,客户端没有发送心跳帧维持连接,断开后短时间内重连可能被服务器限制,导致立即失败。
修复方案
1. 重置WebSocket流与Strand
在close()方法后重新初始化WebSocket流对象,确保每次重连使用全新的strand和流状态:
void WebSocketClientOrder::reconnect() { close(); // 手动析构旧流对象并重新初始化 ws_.~stream(); new (&ws_) websocket::stream<asio::ssl::stream<tcp::socket>>( asio::make_strand(io_context_), ws_.next_layer().get_ssl_context() ); // 异步延迟重连,避免阻塞IO线程 asio::steady_timer timer(io_context_); timer.expires_after(std::chrono::seconds(RECONNECTION_TIMEOUT)); timer.async_wait([this](beast::error_code ec) { if (!ec) { connect(host_, port_); } }); }
2. 替换同步sleep为异步延迟
移除std::this_thread::sleep_for,改用Boost.Asio的steady_timer实现异步延迟,确保IO上下文始终能处理事件。
3. 重连时清空消息队列
在重连前清空消息队列,避免旧消息在新连接中错误发送:
void WebSocketClientOrder::reconnect() { close(); // 清空消息队列 { std::lock_guard<std::mutex> lock(message_queue_mutex_); while (!message_queue_.empty()) { message_queue_.pop(); } } // ... 异步延迟重连代码 }
4. 添加心跳机制
定期发送ping帧维持连接,避免服务器因无交互断开连接:
// 类内新增成员变量 asio::steady_timer heartbeat_timer_; const std::chrono::seconds HEARTBEAT_INTERVAL = std::chrono::seconds(30); // 构造函数初始化定时器 WebSocketClientOrder::WebSocketClientOrder(asio::io_context& io_context, asio::ssl::context& ssl_context, std::string api_key, std::string api_secret) : io_context_(io_context) , resolver_(io_context) , ws_(asio::make_strand(io_context), ssl_context) , heartbeat_timer_(io_context) , api_key_(std::move(api_key)) , api_secret_(std::move(api_secret)) {} // 心跳发送函数 void WebSocketClientOrder::send_heartbeat() { if (!ws_.is_open()) { return; } ws_.async_ping("", [this](beast::error_code ec) { if (!ec) { heartbeat_timer_.expires_after(HEARTBEAT_INTERVAL); heartbeat_timer_.async_wait([this](beast::error_code ec) { if (!ec) { send_heartbeat(); } }); } }); } // 连接成功后启动心跳 void WebSocketClientOrder::connect(const std::string& host, const std::string& port) { host_ = host; port_ = port; try { auto results = resolver_.resolve(host, port); asio::connect(ws_.next_layer().next_layer(), results); if (!SSL_set_tlsext_host_name(ws_.next_layer().native_handle(), host.c_str())) { throw beast::system_error( beast::error_code(static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category()), "[WSCO]: Failed to set SNI" ); } ws_.next_layer().handshake(asio::ssl::stream_base::client); ws_.handshake(host + ":" + port, DEFAULT_URI); std::cout << "[WSCO]: Connected to " << host << std::endl; authenticate(); do_read(); send_heartbeat(); // 启动心跳 } catch (const std::exception& e) { std::cerr << "[WSCO]: Connection failed: " << e.what() << ". Retrying..." << std::endl; reconnect(); } }
5. 优化close方法的资源清理
确保彻底清理所有层级的连接状态,取消定时器避免残留事件:
void WebSocketClientOrder::close() { beast::error_code ec; // 关闭WebSocket if (ws_.is_open()) { ws_.close(websocket::close_code::normal, ec); if (ec && ec != beast::errc::not_connected) { std::cerr << "[WSCO]: Close failed: " << ec.message() << std::endl; } } // 关闭SSL流 ws_.next_layer().shutdown(ec); if (ec && ec != asio::error::eof && ec != asio::ssl::error::stream_truncated && ec != beast::errc::not_connected) { std::cerr << "[WSCO]: SSL shutdown failed: " << ec.message() << std::endl; } // 关闭底层socket ws_.next_layer().lowest_layer().close(ec); if (ec && ec != beast::errc::not_connected) { std::cerr << "[WSCO]: Socket close failed: " << ec.message() << std::endl; } // 取消心跳定时器 heartbeat_timer_.cancel(ec); }
内容的提问来源于stack exchange,提问作者stas stas
相关产品推荐
相关产品推荐

