hyperf 3.0 websocket 必须在 onclose 中显式 close 连接并清理定时器、缓存等关联资源,推荐用 defer $conn->close();需配置 swoole 与 nginx 心跳超时对齐,禁用 tcp_nodelay 以保障心跳及时性。

Hyperf 3.0 的 WebSocket 服务基于 Swoole 协程,连接断开后若不主动释放资源,会导致文件描述符(FD)堆积、内存持续增长、goroutine 泄漏——这不是“偶尔断连”的小问题,而是协程生命周期失控的典型表现。
必须在 onClose 回调中显式关闭连接
Hyperf 不会自动帮你 close 连接。即使客户端已断开,Swoole 的 WebSocket\Frame 对象和底层 socket FD 仍驻留,直到进程重启。
- 务必在自定义的
onClose方法里调用$this->server->close($fd)或$conn->close() - 不要依赖
try...catch或finally:协程中断时,finally可能不执行 - 推荐写法:
defer $conn->close();放在 handler 函数最开头,确保无论读写是否出错都会触发
清理协程内关联资源:定时器、缓存、上下文引用
一个连接常携带心跳定时器、用户 session、Redis 连接池实例、Channel 管道等。这些不会随连接关闭自动销毁。
- 把定时器 ID 挂到
$conn上(如$conn->heartbeatTimer = $this->tick(25000, ...)),然后在onClose中clearTimer($conn->heartbeatTimer) - 从全局容器(如
ConnectionPool或ConcurrentMap)中unset当前$fd对应条目 - 避免闭包捕获
$this或大对象:用弱引用或传参方式解耦,防止整个 handler 实例被锁死在内存中
处理异常中断:超时、心跳失败、读写错误要统一兜底
真实场景中,onClose 并非总能被触发。网络 RST、客户端强退、Nginx 主动 kill 都可能导致连接静默消失。
- 在
onMessage和读循环中,遇到err !== null必须break并立即close($fd),否则协程卡住不退出 - Swoole 的
heartbeat_idle_time触发时,会直接close连接,但你仍需监听onClose做业务清理 - 建议加一层守护协程:
go function() { while ($conn->isEstablished()) { co::sleep(1); } $this->cleanup($fd); },作为最后防线
配置层对齐:避免中间件提前掐断连接
资源释放再彻底,也扛不住 Nginx 或 ALB 在半路把连接干掉。
- 确认
server.php中heartbeat_check_interval = 25,heartbeat_idle_time = 60,差值 ≥20 秒 - Nginx 必须设
proxy_read_timeout 86400和proxy_send_timeout 86400,且启用Upgrade头透传 - 禁用 Swoole 的
open_tcp_nodelay = false(默认 true),避免 Nagle 算法拖慢心跳响应











