最准的统计方式是直接读 $server->connections,它是内核级实时快照,v5.0+为channel类型,v4.x为迭代器,天然抗异常;需配合心跳检测、redis聚合与定时校验确保准确性。

直接读 $server->connections 是最准的统计方式
在 Swoole 的 WebSocket 服务器中,$server->connections 是一个只读的 Swoole\Coroutine\Channel 类型(v5.0+)或数组式迭代器(v4.x),它实时反映当前所有已建立、未关闭的 WebSocket 连接对应的 fd。它不依赖心跳、不靠计数器增减,底层由 epoll/kqueue 状态驱动,只要连接还活着,就一定在里面。
常见错误是自己维护静态变量 $onlineCount:一旦 onClose 没触发(如客户端强杀进程、网络闪断)、或 onOpen 被重复调用(如重连未去重),数字立刻失真。而 $server->connections 是内核级快照,天然抗异常。
- 必须在
onOpen或onMessage回调里调用,不能在协程外或定时器里直接遍历(v5.0+ 需co::sleep(0)让出调度) - v4.8+ 支持
count($server->connections);v5.0+ 推荐用$server->connections->length(),更明确且性能略优 - 注意:这个集合只含 WebSocket 连接,不包括普通 HTTP 请求或 TCP 连接
集群环境下不能只看单节点 $server->connections
单机部署时,count($server->connections) 就是真实在线数;但多台 Swoole 实例组成集群后,每个节点的 $server->connections 只反映本机连接,总人数必须聚合。
Redis 是最常用且可靠的聚合方案,不是因为“高级”,而是因为它解决了三个硬需求:原子性(INCR/DECR)、跨进程可见性、以及可主动探活(配合 TTL 剔除僵尸连接)。
- 在
onOpen里执行redis->incr('ws:total'),并给 key 设置 60 秒 TTL - 在
onClose里执行redis->decr('ws:total') - 另起一个定时任务(如每 10 秒),用
redis->keys('ws:fd:*')扫描所有连接标识,比对$server->connections是否仍存活,不一致则补DECR - 避免用 Pub/Sub 广播连接数变更——它不保证送达,会导致前端显示滞后或跳变
onClose 不一定被调用,得靠心跳兜底
客户端关浏览器标签、杀 App、断网,服务端未必能立刻收到 onClose。Swoole 默认不会主动探测死连接,$server->connections 会残留失效 fd,导致统计虚高。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
解决办法是开启 heartbeat_check_interval 并设置合理的 heartbeat_idle_time:
- 启动服务器时配置:
$server->set(['heartbeat_check_interval' => 30, 'heartbeat_idle_time' => 60]) - 这会让 Swoole 每 30 秒 ping 所有连接,60 秒没 pong 就自动 close 并触发
onClose - 务必确认客户端实现了
onpong响应,否则心跳失败会被误判为断连 - 不要把
heartbeat_idle_time设得太小(如
前端展示前别忘了格式化和防抖
服务端推送的数字只是原始计数,直接扔到页面上容易引发 UX 问题:比如从 99 跳到 100 再跳回 99,或者连续收到 5 条相同数字的消息造成重绘浪费。
建议在客户端做两层处理:
- 用
Map缓存最近 3 秒内收到的所有count值,只取最新一条更新 DOM - 数字显示加千分位:
Intl.NumberFormat().format(count);为 0 时显示“无人在线”而非“0” - 如果 5 秒内没收到新消息,显示“连接中…”并尝试
ws.close(); new WebSocket(...),而不是等超时 - 禁止前端自行发
{type: "join"}类消息来“声明上线”——一切以服务端连接池为准
真正难的不是拿到那个数字,而是让这个数字在各种异常网络条件下依然可信。每次看到 count($server->connections) 返回结果,都要默认它可能多算了一个——然后用心跳、Redis、定时校验三层机制把它拉回来。










