Boost Beast async_write搭配队列使用时空队列致发送循环中断问题
问题根因
当前的写流程完全依赖on_write回调触发下一次do_write执行:当消息队列为空时,do_write不会发起async_write调用,也就不会产生新的on_write回调,整个写循环就此终止。后续新消息入队时没有触发点重新启动写流程,就会出现消息堆积无法发送的问题。
额外需要注意:Boost.Asio 不允许对同一个 socket 同时发起多个异步写操作,当前代码如果在写过程中有新消息入队,也没有并发保护,存在线程安全风险。
修复方案
1. 新增成员变量
给session类新增2个成员变量:
- 互斥锁
queue_mutex_保护消息队列的并发访问 - 布尔标记
is_writing_记录当前是否有正在进行的异步写操作,避免重复发起写请求
// 原有成员变量 std::queue<nlohmann::json> outgoingMessages; // 新增成员变量 std::mutex queue_mutex_; bool is_writing_ = false;
2. 新增线程安全的消息入队接口
所有新消息必须通过这个接口入队,入队后如果当前没有运行中的写操作,就主动触发do_write启动写循环:
void session::push_message(nlohmann::json msg) { std::lock_guard<std::mutex> lock(queue_mutex_); outgoingMessages.push(std::move(msg)); // 没有正在进行的写操作,主动启动写流程 if (!is_writing_) { do_write(); } }
3. 修改do_write和on_write逻辑
调整逻辑维护is_writing_状态,保证线程安全,同时避免异步写访问野指针:
void session::do_write(void) { std::lock_guard<std::mutex> lock(queue_mutex_); if (outgoingMessages.empty()) { // 队列已空,标记写操作结束,直接返回 is_writing_ = false; return; } // 标记正在写,避免重复发起异步写 is_writing_ = true; auto message = std::move(outgoingMessages.front()); outgoingMessages.pop(); // 用shared_ptr持有序列化后的字符串,避免临时变量提前销毁导致野指针 auto serialized_msg = std::make_shared<std::string>(message.dump()); ws_.async_write( boost::asio::buffer(*serialized_msg), boost::beast::bind_front_handler(&session::on_write, shared_from_this()) ); } void session::on_write(boost::beast::error_code errorCode, std::size_t bytes_transferred) { if (errorCode) return fail(errorCode, "write"); if (bytes_transferred == 0) std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 触发下一次写操作 do_write(); }
如果你的整个session逻辑都跑在同一个asio线程上下文里,不需要跨线程操作队列,也可以去掉互斥锁,只保留is_writing_标记即可。
内容的提问来源于stack exchange,提问作者Nicholas J Ingrassellino
相关产品推荐
相关产品推荐

