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
相关产品推荐
相关产品推荐

