直接用GatewayWorker,它已封装连接路由、分组广播等90%重复逻辑;纯Workerman需手动维护跨进程连接映射,无法天然支持分布式群发,易漏消息、状态不同步。

直接用 GatewayWorker,别自己手写连接池和广播逻辑——它专为这类场景设计,且已解决 UID 绑定、进程间通信、断线重连等实际问题。
为什么不用纯 Worker 自己实现群发
纯 Worker 实例里维护的 $worker->connections 或自定义 $worker->userList 只在当前进程内有效。单机多进程($worker->count = 4)时,A 进程里的用户根本不知道 B 进程里连着谁,群发必然漏人。你看到的“群发成功”只是局部广播,线上一扩容就失效。
常见错误现象包括:
- 前端收不到推送,但服务端日志显示“已 send”
- 同一用户登录两次(不同浏览器),只收到一次消息
- 重启某个 worker 进程后,部分用户离线状态不更新
这些都不是代码 bug,而是架构局限。
GatewayWorker 的最小可用部署结构
必须包含三个角色:Register(注册中心)、Gateway(网关)、Business(业务逻辑)。三者可同机,但进程分离:
-
register:监听127.0.0.1:1238,不做业务,只管心跳和路由 -
gateway:监听客户端 WebSocket 连接(如ws://your-domain.com:8282),不处理业务逻辑 -
business:独立进程,负责接收 HTTP 请求(比如后台点击“群发”),调用Gateway::sendToAll()或Gateway::sendToUid()
关键点:business 进程必须通过 GatewayClient 与 register 通信,而不是直连 gateway 进程。否则跨进程消息发不出去。
业务层推送代码怎么写才可靠
在你的通知后台控制器里(比如 ThinkPHP 的 NotifyController),引入 GatewayClient 后,重点不是“怎么发”,而是“发给谁”和“发失败怎么办”:
-
Gateway::$registerAddress必须设成真实 IP(不能是0.0.0.0或域名),否则GatewayClient连不上 register - 群发前建议先查在线状态:
Gateway::isOnline($uid),避免向离线用户空转 - 向指定部门群发?得自己维护
uid → group映射表,Gateway不管分组逻辑 - 如果推送量大(比如 5000+ 人),不要循环调用
sendToUid(),改用sendToGroup()+ 前端按 group 订阅
示例片段(非完整类):
use GatewayClient\Gateway;
Gateway::$registerAddress = '127.0.0.1:1238';
// 向教务处所有在线教师推送
$uids = Db::table('users')->where('dept', 'jwc')->column('uid');
foreach ($uids as $uid) {
if (Gateway::isOnline($uid)) {
Gateway::sendToUid($uid, json_encode(['type'=>'notice','content'=>'课表已更新']));
}
}
前端如何稳定接收并区分消息类型
WebSocket 连接由 gateway 统一维持,但消息内容必须带业务标识,否则前端无法判断是“系统通知”还是“聊天消息”:
- 服务端发的数据必须是字符串或 JSON,别传 PHP 数组(
sendToUid()会序列化失败) - 推荐统一格式:
{"event":"notice","data":{"title":"...","time":1716364320}} - 前端
onmessage收到后,用JSON.parse()解析,再根据event字段分发处理函数 - 别依赖
connection.id做前端状态管理——这个 ID 是 gateway 进程内的临时编号,刷新页面就变
容易被忽略的一点:如果学校/企业内网有代理或防火墙,ws:// 协议可能被拦截,此时得配 WSS(TLS)并确保证书可信,否则 Chrome 会静默关闭连接。

















