必须主动调用conn.Close()释放资源,ReadMessage返回io.EOF或1006即表示连接已不可用,需立即关闭并退出所有相关goroutine,否则导致fd和goroutine泄漏。

半关闭连接不会自动释放资源,必须主动调用 conn.Close(),否则 fd 和 goroutine 会持续泄漏。
ReadMessage 返回 io.EOF 或 websocket: close 1006 是半关闭信号
当服务端调用 conn.Close() 或 TCP 连接被静默中断(如 NAT 超时、LB 主动踢出),客户端的 ReadMessage() 通常不会阻塞,而是立即返回 io.EOF 或 websocket: close 1006 (abnormal closure)。这不是“读完了”,而是连接已不可用的明确信号。
- 不要只检查
err != nil就跳过处理——1006必须当作断连处理,不是临时错误 - 不要在 error 分支里继续调用
ReadMessage()或WriteMessage(),此时conn已处于无效状态 - 即使你没显式关连接,也要在收到这类错误后立刻执行
conn.Close(),否则底层net.Conn不会释放
defer conn.Close() 在长生命周期 goroutine 中完全失效
很多代码在 handler 函数开头写 defer conn.Close(),这仅对函数退出生效;而 WebSocket 客户端常驻 goroutine(如 readLoop)可能运行数小时,defer 根本不触发。
- 必须在检测到断连(如
ReadMessage报错)后,**同步调用conn.Close()** - 如果同时有写 goroutine,需通过 channel 或
sync.Once确保只关一次,避免重复 close 导致 panic - 关闭前可先发
websocket.CloseMessage,但不是必须;重点是关完之后,所有基于该conn的 goroutine 都得退出
旧连接未 Close 会导致新重连失败或 fd 耗尽
Linux 默认每个进程最多打开 1024 个文件描述符,WebSocket 连接每个占一个 fd。若每次断连都不 Close(),几十次重连后就会卡在 dial: too many open files。
立即学习“go语言免费学习笔记(深入)”;
- 重连前务必确认旧
conn已关闭:可在结构体中加closed sync.Once+mu sync.RWMutex保护 - 用
lsof -p $(pidof yourapp)实时观察 fd 数量,验证是否泄漏 - gorilla/websocket 的
conn.UnderlyingConn()可用于调试,但生产环境别依赖它做状态判断
最易被忽略的一点:心跳 goroutine 和读 goroutine 常共用一个 conn,但它们的生命周期不同步。一个挂了,另一个可能还在往已关闭的 conn 发 PingMessage,导致日志刷屏 use of closed network connection —— 这说明你没在关连接时顺手停掉所有相关 goroutine。


















