think-swoole 全局广播
在 think-swoole 中,全局广播(向当前服务器上所有连接的客户端发送消息)通常有两种实现方式。
第一种是利用 think-swoole 内置的 Websocket 门面(Facade)或事件机制,适合单机部署;第二种是结合 Redis 发布/订阅(Pub/Sub),适合分布式/多机部署。
方案一:单机部署(利用内置 to() 方法)
如果你的项目只部署在一台服务器上,think-swoole 提供了非常简单的 API 来实现全员广播。
1. 核心控制器/事件代码
在你的 WebSocket 事件处理类(或控制器)中,使用 to() 方法并传入一个不存在的特定值(如空数组或 0),或者不指定特定的 fd,配合 emit 发送。但在 think-swoole 标准 API 中,最稳妥的广播写法是留空 to()的参数(部分版本支持),或者通过当前进程循环所有的连接。
最标准的 think-swoole 内置广播写法如下:
php
<?php
namespace app\listener;
use think\swoole\Websocket;
class NewMessage
{
protected $websocket;
// 注入 Websocket 实例
public function __construct(Websocket $websocket)
{
$this->websocket = $websocket;
}
public function handle($event)
{
// 准备要广播的数据
$data = [
'type' => 'broadcast',
'message' => '这是一条面向所有人的系统广播消息!'
];
// 核心:直接调用 emit,不指定 to(),即为向当前房间或全局广播
// 如果使用了房间(Room)机制,请确保没有将连接隔离
$this->websocket->emit('message_event_name', $data);
}
}
请谨慎使用此类代码。
2. 借助 Swoole Server 原生广播(100% 成功率单机方案)
有时候因为封装问题,内置的 emit 可能会受到当前客户端上下文的限制。如果你想在任意地方(如普通的 HTTP 控制器、定时任务)强制向单机全员广播,可以直接获取底层的 Swoole Server 实例进行遍历:
php
<?php
namespace app\controller;
use app\BaseController;
use think\swoole\Manager;
class Broadcast extends BaseController
{
public function sendSystemNotice(Manager $manager)
{
// 获取 Swoole Server 实例
$server = $manager->getServer();
$data = json_encode([
'event' => 'system_notice',
'data' => ['msg' => '服务器即将于凌晨维护!']
]);
// 遍历当前服务器所有的 WebSocket 连接并发送
foreach ($server->connections as $fd) {
// 确保该 fd 是一个有效的 WebSocket 客户端连接
if ($server->isEstablished($fd)) {
$server->push($fd, $data);
}
}
return json('广播发送成功');
}
}
请谨慎使用此类代码。
方案二:分布式部署(Redis Pub/Sub 方案 🌟 推荐)
正如上一个问题提到的,如果你的项目未来有多个 Worker 进程或者多台服务器集群,单纯用方案一会导致“只有连接在当前进程/当前服务器的用户能收到消息,其他服务器的用户收不到”。
利用 Redis 可以完美解决这个问题:
1. 任意地方触发广播(发布者)
不管是 HTTP 请求、队列、还是某个 WebSocket 进程,只需要往 Redis 频道投递一条消息:
php
use think\facade\Cache;
// 借助 Redis 的原生驱动发布消息到 'websocket_broadcast' 频道
$redis = Cache::store('redis')->handler();
$payload = json_encode([
'event' => 'global_notice',
'msg' => '全服公告:恭喜玩家XXX获得神装!'
]);
$redis->publish('websocket_broadcast', $payload);
请谨慎使用此类代码。
2. 在 Swoole 启动时监听 Redis(订阅者)
你需要让 think-swoole 开启一个自定义进程或在 onWorkerStart 中异步订阅这个 Redis 频道。一旦收到消息,立即调用本地的推送到所有连接。
在 config/swoole.php 的 websocket.listen 或自定义中途挂载:
php
// 在 app\listener\SwooleWorkerStart.php (监听 swoole.workerStart 事件)
namespace app\listener;
use think\swoole\Manager;
use Swoole\Coroutine;
class SwooleWorkerStart
{
public function handle(Manager $manager)
{
$server = $manager->getServer();
// 仅在第一个 Worker 进程中开启订阅,防止重复消费(或者使用群发)
// 如果是多台服务器,每台服务器都需要有一个进程订阅
Coroutine::create(function () use ($server) {
$redis = new \Redis();
$redis->connect('127.0.0.1', 6373);
// 阻塞监听 Redis 频道
$redis->subscribe(['websocket_broadcast'], function($redis, $chan, $msg) use ($server) {
// 收到 Redis 的广播通知,遍历本地所有客户端连接推送
foreach ($server->connections as $fd) {
if ($server->isEstablished($fd)) {
$server->push($fd, $msg);
}
}
});
});
}
}
请谨慎使用此类代码。
💡 总结建议
小型单机项目:直接使用 方案一中的方法 2(遍历 $server->connections),代码最少,性能最高,不会漏发。
中大型/集群项目:必须走 方案二(Redis 发布订阅),这样无论用户连在 A 服务器还是 B 服务器,都能同时收到这笔广播。