OnClose不执行主因是连接未正确注册或生命周期错位,如协程异常退出、非标准中断、HTTP/2启用导致升级不稳定;需确保正常close、注册fd、禁用HTTP/2,并在OnClose中释放资源、清除定时器、同步状态。

Hyperf 的 WebSocket 连接断开后不会自动重连,OnClose 回调是唯一可靠的清理入口,但默认不触发或失效往往是因为连接未被正确注册或生命周期管理错位。
为什么 OnClose 有时根本不执行
Hyperf 的 OnClose 是 Swoole 层回调,依赖连接在 Server 中的存活状态。如果连接被提前 close()、协程异常退出、或使用了非标准方式(如直接 exit 或 die)中断请求,Swoole 可能无法捕获关闭事件。
- 确保 WebSocket 连接是通过
$server->push()或$server->close()正常关闭,而非抛出未捕获异常导致协程崩溃 - 检查是否在
onOpen中漏掉了$fd到Channel/Table/Redis的注册逻辑 —— 若无记录,OnClose里查不到上下文,容易误判为“没触发” - 禁用
swoole.server.settings.open_http2_protocol = true(若启用),HTTP/2 模式下 WebSocket 升级行为不稳定,OnClose可能丢失
OnClose 中必须做的三件事
不能只写日志或打印,要覆盖资源释放、状态同步、防重复清理三个维度。
- 从共享存储中移除
$fd:比如从Coroutine\Table删除行,或执行redis->hDel('ws:clients', $fd) - 取消关联的定时器:
Timer::clear($this->timerId[$fd] ?? null),否则残留定时器会持续触发并报Invalid timer id - 广播下线事件时加锁或判空:
if ($this->userTable->exist($uid)) { ... },避免因并发关闭导致重复推送或Key not exists报错
如何让客户端感知断开并可控重连
服务端不重连,但可以推动客户端重连策略 —— 关键是利用 OnClose 主动发一次带重连提示的包,再配合心跳超时兜底。
- 在
OnClose中尝试最后一次$server->push($fd, json_encode(['event' => 'disconnect', 'retry' => true]))(需判断$server->exist($fd)防 crash) - 前端 WebSocket 实例监听
onclose,收到该 payload 后延迟 1–3 秒调用new WebSocket(url),避免雪崩重连 - 服务端心跳检测(如
Ping/Pong)应在onMessage中刷新时间戳,OnClose不负责心跳清理,那是tick定时器的工作
真正容易被忽略的是:Hyperf 的 WebSocket\Sender 在连接关闭后仍可能缓存未发送消息,OnClose 里调用 Sender::delete($fd) 才算彻底解绑;否则下次同 fd 复用时会误推旧数据。


















