think-swoole 自带的 Room
改用 think-swoole 自带的 Room(房间) 机制是解决线上高频广播丢包、阻塞的最正确姿势。
因为 Room 的底层完全基于 Swoole Table(内存共享表) 实现。当你要全员广播时,它不需要走传统的 Task进程,也不需要通过复杂的管道(Pipe)跨进程通信,更不需要你在代码里写 foreach 循环。它会直接在内存中检索当前在线的 FD,利用底层 C++ 级别的事件轮询组播(Multicast)发出去,性能是百倍级别的提升。
以下是配合 Redis 发布订阅,在 think-swoole 中使用 Room 实现“全员高频推送”的完整落地标准代码。
第一步:用户连接时,自动加入全员房间
在你的 WebSocket 事件监听器(通常是 app\listener\WebsocketConnect.php 或你在 handleHttpRequest握手成功后的逻辑中),将上线的用户加入到一个固定的全局房间。
php
namespace app\listener;
use think\swoole\Websocket;
class WebsocketConnect
{
protected $websocket;
public function __construct(Websocket $websocket)
{
$this->websocket = $websocket;
}
public function handle($event)
{
// 1. 获取当前连接的客户端 FD
// 2. 将该用户加入名为 'all_online_users' 的全员房间
$this->websocket->join('all_online_users');
// 顺便可以给客户端回一个连接成功的通知
$this->websocket->emit('message', 'Connected to broadcast room.');
}
}
请谨慎使用此类代码。
第二步:利用 Redis 订阅,收到消息后通过 Room 广播
由于你的服务可能是多实例或者多进程部署,当 Redis 监听到发布的消息时,直接在当前进程上下文中使用 to('room')->emit() 扩散出去。
在你的订阅消费者或者自定义的 Swoole 进程中,处理逻辑应该精简成这样:
php
namespace app\common\swoole\websocket;
use think\swoole\Websocket;
use app\BaseController;
class Service
{
protected $websocket;
public function __construct(Websocket $websocket)
{
$this->websocket = $websocket;
}
/**
* 当 Redis 收到发布订阅的消息时调用此方法
*/
public function onRedisMessage($message)
{
// ❌ 彻底废弃原本的 $server->task() 或者 foreach($server->connections)
// ✅ 直接利用 Room 机制进行全员无阻塞广播
// to() 指定房间,emit() 发送事件名和数据(或者使用 push 方法)
$this->websocket->to('all_online_users')->emit('broadcast_notice', [
'content' => $message,
'timestamp' => time()
]);
}
}
请谨慎使用此类代码。
第三步:依然要优化的线上参数(配合 Room)
虽然 Room 规避了 Task 队列满和 Pipe 管道挤爆的问题,但是高频发布时,由于前端网络不可控,Swoole 往网卡发送数据的速度如果大于网卡流出的速度,依然会触发单连接的发送缓冲区溢出。
请确保你的 config/swoole.php 配置中调大了以下参数,彻底为高频推送护航:
php
'options' => [
// 尽管不用 Task 发消息,但原有的 Task 数量可以保留做其他异步业务
'task_worker_num' => 8,
// 核心网络调优:
'buffer_output_size' => 64 * 1024 * 1024, // 64MB 单次输出缓冲区。防止高频大消息卡在发送队列
'socket_buffer_size' => 128 * 1024 * 1024, // 128MB Socket缓冲区。允许弱网用户产生一定的消息积压而不被直接踢掉
],
请谨慎使用此类代码。
⚠️ 使用 Room 后的最后一个高频注意点
当前端用户断开连接、刷新页面、或者网络波动断线时,think-swoole 默认会自动把该 FD 从所有 Room 房间中移除。
但在极高频的场景下,为了确保内存表不残留死连接,强烈建议你在 WebsocketClose(连接关闭事件)中显式地执行一次解绑,做一次双保险:
php
// 在 WebsocketClose 监听器中
public function handle($event)
{
// 显式离开全员房间,释放内存引用
$this->websocket->leave('all_online_users');
}