workerman4中onclose未触发并非未断开,而是tcp异常终止导致服务端未收到websocket关闭帧;根本原因包括客户端强退、中间件静默断链、nginx超时配置不当及缺乏主动心跳检测,须通过服务端心跳+超时清理机制保障断线感知。

Workerman4 WebSocket 服务中,客户端断线但服务端 onClose 事件未触发,通常不是“没发生断开”,而是连接异常终止后,服务端根本没收到标准的 WebSocket 关闭帧(close frame),导致 Workerman 无法进入正常的关闭流程。根本原因在于:TCP 连接已断,但应用层未走 WebSocket 协议级关闭流程。
服务端未收到关闭帧的常见原因
WebSocket 的 onClose 仅在收到合法的关闭帧(opcode=8)并完成握手关闭时触发。以下情况会导致它完全不执行:
- 客户端进程被强制杀死(如浏览器崩溃、App 强退、kill -9 进程),TCP 连接直接 RST 或 FIN 未携带关闭帧
- 网络中间件静默丢弃连接:NAT 超时、防火墙主动踢出空闲连接、CDN/反向代理(如 Nginx)在超时后直接断链,不转发关闭帧
-
客户端未调用
ws.close(),而是直接关闭页面/标签页或切后台,部分浏览器(尤其移动端)不会发送关闭帧 - TCP 半开连接未被探测到:客户端已掉线,但服务端 TCP socket 仍处于 ESTABLISHED 状态,Workerman 不会主动检测,也就不会触发任何事件
Workerman 自身机制限制
Workerman 默认不开启心跳检测,也不主动探测连接存活状态。它依赖操作系统通知连接断开(如 recv 返回 0 或 -1)。但在以下场景下,操作系统可能长期不通知:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 客户端断网但未发 FIN/RST(如拔网线、Wi-Fi 切换瞬间)
- 移动设备休眠,socket 被系统挂起,无数据收发也无错误返回
- 云环境 NAT 映射老化(常见于阿里云 SLB、腾讯云 CLB),连接空闲 60–300 秒后被网关单向切断
如何确认和修复
先验证是否真没触发 —— 在 onClose 中加日志并配合 onError 和连接池监控:
- 启用
Worker::$logFile并在onClose打印连接 ID 和时间,同时检查onError是否有errno=104 (Connection reset by peer)或errno=0 (Success, connection closed) - 在
onMessage中记录最后活跃时间,在定时器中扫描超时连接(如 30 秒无心跳),手动$connection->close(),此时才会走onClose - 务必实现服务端心跳:要求客户端每 25 秒发一次 ping,服务端收到后立即 pong;若连续两次未收到 ping,主动 close 连接 → 此时
onClose必然触发 - Nginx 反代需显式配置:
proxy_read_timeout 60;、proxy_send_timeout 60;,并关闭proxy_http_version 1.1;和proxy_set_header Upgrade $http_upgrade;的遗漏
本质上,onClose 不触发不是 Bug,而是 WebSocket 协议和 TCP 层的自然表现。真正可靠的断线感知,必须靠主动心跳 + 超时清理,不能依赖被动事件。









