不能直接遍历$server->connections调用push,因其返回的fd不保证有效,同步推送会阻塞协程、漏发、超时甚至假死;应采用协程分片+isestablished校验的平滑广播方案。

直接遍历 $server->connections 并调用 $server->push() 是最基础的群发方式,但线上环境几乎不能这么用——容易卡主进程、漏发、触发超时、甚至让整个服务假死。
为什么不能直接 foreach($server->connections) 发送?
因为 Swoole 的 $server->connections 是一个迭代器,返回的是所有已建立连接的 fd(文件描述符)集合,但它不保证连接仍有效、未断开、未超时、未被客户端强制关闭。更关键的是:同步逐个 push 会阻塞当前协程或进程,尤其在万级连接时,一次广播可能耗时数秒,期间新连接、心跳、消息全被挂起。
- 客户端已断开但 fd 还在
connections中(常见于网络抖动、浏览器强制关闭) -
$server->push($fd, $msg)在连接异常时会抛出 warning 或静默失败,不报错也不重试 - 单次广播若含大消息(如 JSON 超过 1MB),
push内部会同步写 socket,极易阻塞 - 多 worker 进程下,
$server->connections只返回本进程内的连接,跨进程无法触达
用协程分片 + 连接校验实现平滑广播
核心是把“全量推送”拆成小批次,并用 go 并发执行,同时用 $server->isEstablished($fd) 做连接存活判断。这是 Swoole 官方推荐的生产级做法。
示例代码片段(放在 onWorkerStart 或定时任务中):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
go(function () use ($server) {
while (true) {
co::sleep(10); // 每10秒广播一次
$clients = $server->connections;
$chunks = array_chunk($clients, 50); // 每批50个 fd
foreach ($chunks as $chunk) {
go(function () use ($chunk, $server) {
foreach ($chunk as $fd) {
// 必须校验,否则 push 可能失败或阻塞
if ($server->isEstablished($fd)) {
$server->push($fd, '【系统广播】时间:' . date('H:i:s'));
}
}
});
}
}
});
- 分片大小(如 50)需根据平均消息体积和延迟要求调整;万级连接建议 ≤100
- 必须启用协程:
'enable_coroutine' => true,否则go不生效 -
$server->isEstablished($fd)比$server->exist($fd)更准,它检查 TCP 连接是否真正可写 - 不要在
onMessage里直接广播——高频触发易压垮协程调度器
跨进程广播必须用 Channel 或 Redis
当 worker_num > 1 时,每个 worker 只能看到自己进程内的 fd。想让 A 进程收到的消息也能推给 B 进程里的用户,必须走进程间通信:
- 用
Swoole\Coroutine\Channel:轻量,适合单机多 worker,但需手动管理投递与消费逻辑 - 用
Redis pub/sub:解耦强,天然支持横向扩展,推荐搭配redis->publish('broadcast', $msg)+ 各 worker 订阅 - 用
Swoole\Table存全局在线列表:适合做房间/分组,但广播仍需各 worker 自行遍历本地连接 - 直接上
GatewayWorker:它封装了 Gateway-Register-Worker 三层,广播由 Gateway 统一转发,省心但引入额外架构复杂度
别忽略 onClose 和心跳清理
群发效果差,80% 源于连接没及时清理。Swoole 不会自动从 $server->connections 移除已断开的 fd,必须靠 onClose 回调或心跳检测来维护真实在线状态。
- 务必实现
onClose,哪怕只写echo "fd {$fd} closed\n";,用于确认回调是否触发 - 开启心跳:
'heartbeat_check_interval' => 60, 'heartbeat_idle_time' => 600,让 Swoole 主动踢掉失活连接 - 如果业务需要精确在线人数,不能依赖
count($server->connections),而应维护一个Swoole\Table或 Redis Set,在onOpen/onClose里增删
真正难的不是“怎么发”,而是“发给谁”和“发没发成功”——连接状态永远是动态的,没有银弹,只有结合校验、分片、异步和存储的组合策略。










