avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 think-swoole 全局广播
文章

think-swoole 全局广播

发表于 2026-06-26 更新于 2026-06- 26
作者 mdo
11~14 分钟 阅读

在 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 服务器,都能同时收到这笔广播。

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

利用 Swoole 协程特性

上一篇

Task 队列阻塞与丢弃

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。