websocket 内存泄漏源于未显式关闭连接及清理关联资源。必须调用 ws.close()、清除监听器、定时器,并在服务端主动释放连接,封装带 destroy() 的管理器可确保彻底清理。

WebSocket 实时连接本身不“泄漏”,但用法不对,内存和句柄就一定会涨——这不是配置调优问题,而是资源生命周期没管好。
必须显式关闭连接,不能依赖自动回收
只要没调 ws.close()(浏览器端)或 conn.Close()(服务端),底层 TCP 连接就不会断,操作系统文件描述符(FD)持续累积,JS 对象或 Go Conn 实例也卡在堆里无法被 GC。
- Chrome DevTools 的 Memory 面板搜索
WebSocket,能看到堆快照中不断新增的实例,Retainers 显示被闭包、全局数组或未清空的监听器强引用 - Vue/React 组件反复挂载卸载,每次 new WebSocket() 却不销毁旧实例,泄漏呈线性增长
- 即使
ws.readyState === 0(CONNECTING)或3(CLOSED),只要没执行ws.close(),实例仍驻留内存
清理要彻底:连接 + 监听器 + 定时器
只关连接不够。事件监听器、心跳定时器、闭包捕获的 this/state/ref,都会把整个作用域锁死。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 错误写法:
ws.onmessage = (e) => this.handleData(e),卸载时不设ws.onmessage = null,闭包一直活着 - 心跳定时器常见坑:把
heartbeatTimer存在组件 data/state 里,卸载时只清 state,没调clearInterval - 稳妥做法:把 timer ID 挂到 ws 实例上,如
ws.heartbeatTimer = setInterval(...),并在ws.onclose中统一清除:if (ws.heartbeatTimer) clearInterval(ws.heartbeatTimer)
服务端也要主动释放,不能只等客户端断开
客户端关了,服务端不处理,FD 和 goroutine/Conn 对象照样堆积。
- Node.js(ws 库):每个连接出错或超时时,必须调
ws.terminate()或ws.close(),尤其不能漏掉pong超时分支 - Go(gorilla/websocket):务必在 handler 函数开头
defer conn.Close();读消息循环里遇到err != nil必须break,否则 goroutine 永不退出 - PHP(Swoole):
onClose回调里必须手动unset($this->connections[$fd])或调$server->close($fd)
单例与共享连接需自带销毁契约
所谓“全局 WebSocket”本质是单例,但单例 ≠ 永不销毁。没有明确的 destroy() 入口,等于放任所有监听器、定时器、上下文长期驻留。
- 不要指望
beforeUnmount或useEffect清理函数自动触发ws.close()—— 它们不会 - 推荐封装一个带
destroy()方法的连接管理器,内部执行:ws.close()+ws.onmessage = null+ws.onerror = null+clearInterval(ws.heartbeatTimer) - 在 Vue3
onBeforeUnmount或 ReactuseEffect清理函数中,显式调用该destroy()










