WebSocket空闲超时实现疑问:取消async_read后Socket无法复用
问题
我用timer配合async_read实现WebSocket空闲超时,超时后调用ws_.next_layer().cancel()取消async_read,但后续执行ws_.write()时出现operation cancelled错误。请问:
ws_.next_layer().cancel()是否会导致Socket无法用于后续操作?- 有没有其他方法可以捕获空闲超时并退出async_read,同时保持Socket可用无需关闭重连?
代码示例
#include "Socket_Factory_1.h" class TimeoutException : public std::exception { public: explicit TimeoutException(const std::string& message) : message_(message) {} virtual const char* what() const noexcept override { return message_.c_str(); } private: std::string message_; }; WebSocketClient::WebSocketClient(const std::string& host, const std::string& port) : host_(host), port_(port), ioc_(), resolver_(ioc_), ws_(ioc_), timer_(ioc_) {} WebSocketClient::~WebSocketClient() { close(); } void WebSocketClient::connect() { try { ws_.binary(true); auto const results = resolver_.resolve(host_, port_); asio::connect(ws_.next_layer(), results.begin(), results.end()); ws_.handshake(host_, "/"); } catch (const std::exception& e) { std::cerr << "Connect Error: " << e.what() << std::endl; throw; } } void WebSocketClient::send(const void* data, size_t size) { try { ws_.write(asio::buffer(data, size)); } catch (const std::exception& e) { std::cerr << "Send Error: " << e.what() << std::endl; throw; } } std::string WebSocketClient::receive() { std::cout<<"HI1"; read_successful = false; timeout_occurred = false; // Set up the timer std::chrono::seconds timeout_duration(3); timer_.expires_after(timeout_duration); std::cout<<"HI"; // Start an asynchronous read operation ws_.async_read(buffer, [this](beast::error_code ec, std::size_t bytes_transferred) { if (!ec) { result = beast::buffers_to_string(buffer.data()); read_successful = true; // Mark read as successful } else if(ec==asio::error::operation_aborted){ std::cout<<"aborted"<<std::endl; } else { std::cerr << "Read Error: " << ec.message() << std::endl; } } ); // Set up the timeout handler timer_.async_wait( [this](beast::error_code ec) { if (ec != asio::error::operation_aborted) { timeout_occurred = true; // Mark timeout as occurred }else{ std::cout<<"aborted tiemr"<<std::endl; } } ); // Run the IO context to perform the asynchronous operations while (!read_successful && !timeout_occurred) { ioc_.run_one(); // Process one ready handler } buffer.clear(); if (timeout_occurred) { ws_.next_layer().cancel(); ioc_.run(); // ioc_.restart(); std::cerr << "Read operation timed out\n"; throw TimeoutException("Timeout"); } if (read_successful) { timer_.cancel(); return result; } return ""; // Return empty string if neither read nor timeout occurred } void WebSocketClient::close() { try { if (ws_.is_open()) { ws_.close(websocket::close_code::normal); } } catch (const std::exception& e) { std::cerr << "Close Error: " << e.what() << std::endl; } }
解决方案
关于ws_.next_layer().cancel()的影响
是的,调用该方法会取消底层Socket上所有未完成的异步操作,包括后续准备执行的write操作。这个操作会让Socket进入不稳定状态,导致后续所有依赖该Socket的操作都会抛出operation cancelled错误,无法继续使用该Socket进行正常通信。
正确的空闲超时实现方式
要实现超时退出async_read且保持Socket可用,应该使用Beast WebSocket流自带的cancel()方法,仅取消读操作,而非底层Socket。具体步骤如下:
1. 使用WebSocket流的定向cancel操作
调用ws_.cancel(websocket::cancel_type::read)可以仅终止当前的异步读操作,不会影响Socket本身,后续仍可执行write或新的read操作。
2. 修正超时逻辑
在定时器回调中直接执行定向cancel,无需通过标志位延迟处理,确保操作及时响应。
3. 优化IO上下文管理
使用局部状态变量(如原子变量)跟踪操作状态,避免成员变量的线程安全问题;重置IO上下文确保残留handler被清理。
修改后的receive函数示例
std::string WebSocketClient::receive() { std::cout << "HI1\n"; std::atomic<bool> read_successful{false}; std::atomic<bool> timeout_occurred{false}; beast::flat_buffer buffer; std::string result; // 设置超时定时器 std::chrono::seconds timeout_duration(3); timer_.expires_after(timeout_duration); // 启动异步读操作 ws_.async_read(buffer, [this, &read_successful, &result, &buffer](beast::error_code ec, std::size_t bytes_transferred) { if (!ec) { result = beast::buffers_to_string(buffer.data()); read_successful = true; } else if (ec == asio::error::operation_aborted) { std::cout << "read aborted\n"; } else { std::cerr << "Read Error: " << ec.message() << "\n"; } } ); // 超时回调:仅取消WebSocket读操作 timer_.async_wait( [this, &timeout_occurred](beast::error_code ec) { if (!ec) { beast::error_code cancel_ec; // 仅取消读操作,不影响写操作 ws_.cancel(websocket::cancel_type::read, cancel_ec); if (cancel_ec) { std::cerr << "Cancel read error: " << cancel_ec.message() << "\n"; } timeout_occurred = true; } else { std::cout << "timer aborted\n"; } } ); // 运行IO上下文直到操作完成或超时 ioc_.reset(); while (!read_successful && !timeout_occurred) { ioc_.run_one(); } // 清理定时器 beast::error_code cancel_timer_ec; timer_.cancel(cancel_timer_ec); if (timeout_occurred) { std::cerr << "Read operation timed out\n"; throw TimeoutException("Timeout"); } return read_successful ? result : ""; }
关键说明
- 避免直接操作底层Socket的cancel方法,优先使用WebSocket流提供的定向cancel接口。
- 局部原子变量确保多线程环境下的状态正确性(如果存在多线程调用receive的情况)。
- IO上下文的
reset()方法确保每次调用receive时,之前的操作残留不会干扰当前逻辑。
内容的提问来源于stack exchange,提问作者Manish I
相关产品推荐
相关产品推荐

