onclose 在断网/断电时不触发,因 tcp 四次挥手无法完成,服务端收不到 fin/rst;需靠心跳+定时器主动检测并关闭超时连接,不可依赖 onclose 做关键清理。

onClose 为什么在断网/断电后不触发
因为 TCP 连接断开需要四次挥手,而断网或断电时客户端无法发出 FIN 包,服务端收不到任何通知,onClose 就永远不会被调用——这不是 Workerman 的 bug,是 TCP 协议本身的限制。
常见现象包括:设备拔网线、手机切飞行模式、Wi-Fi 突然中断后,$worker->connections 数量不减,连接对象持续滞留内存,onClose 日志完全沉默。
- Workerman 只响应内核通知的连接关闭事件(如收到 FIN 或 RST)
- 没有应用层心跳时,“假在线”状态可能维持数小时甚至更久
- GatewayWorker 框架内置了心跳检测和超时踢出,但纯 Workerman 需手动实现
如何让 onClose 实际可用
不能依赖 onClose 做连接清理,必须叠加心跳机制主动识别失效连接。核心是两件事:记录活跃时间 + 定期扫描淘汰。
- 在
onConnect中为$connection设置属性:$connection->lastMessageTime = time() - 在
onMessage中更新:$connection->lastMessageTime = time() - 用
Worker::addTimer(55, function () { ... })每 55 秒遍历$worker->connections,对time() - $conn->lastMessageTime > 60的连接调用$conn->close() - 注意:不要在定时器里直接
unset($worker->connections[$id]),Workerman 内部会自动清理已 close 的连接
onClose 触发但业务逻辑没执行完
如果 onClose 回调里有耗时操作(比如写数据库、发 HTTP 请求),它会被 Workerman 强制中止——因为该回调运行在事件循环主线程中,且无超时控制。
-
onClose不是“安全退出钩子”,它只保证被调用一次,不保证执行完成 - 避免在其中做阻塞操作;需持久化数据时,改用异步方式(如
AsyncTcpConnection或消息队列) - 若必须同步处理,建议把关键状态提前写入
$connection->session或全局缓存,onClose仅做轻量标记
为什么 reload 后旧连接的 onClose 不生效
Workerman 的 reload 是平滑重启,旧进程仍持有原有连接,新进程不接管这些连接,因此新写的 onClose 回调对旧连接无效。
- 修改
onClose后必须执行完整重启:php start.php stop && php start.php start - 旧连接的
onClose仍按旧代码执行,不会自动升级 - 可通过
ss -tuln | grep :端口确认监听是否已切换到新进程(看 PID 变化)
真正难处理的不是“怎么写 onClose”,而是接受它天生不可靠——所有关键清理动作都得绕开它,靠心跳+定时器+连接元数据来兜底。











