gorilla/websocket 客户端需手动实现重连,关键在分离读/写/重连逻辑、用 context 管控生命周期、配合心跳与指数退避;ReadMessage 错误即断连信号,须用 websocket.IsUnexpectedCloseError 区分是否重试,CloseNormalClosure(1000)不重连,CloseAbnormalClosure(1006)立即重连。

Go 的 gorilla/websocket 客户端不会自动重连,必须自己实现;不加控制的重连会压垮服务端或耗尽本地 fd,关键在于分离读/写/重连逻辑、用 context 管生命周期、配合心跳与指数退避。
ReadMessage 报错就是断连信号,别等“超时”
gorilla/websocket 不维护连接活跃状态,conn.ReadMessage() 一出错,基本就是断了。常见错误包括:*websocket.CloseError(带 Code 字段)、io.EOF、net.OpError(如 dial tcp: i/o timeout)。不要只判断 err != nil 就退出——得区分是否真断连:
- 用
websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway)判断是否该重试 -
websocket.CloseNormalClosure(1000)和websocket.CloseAbnormalClosure(1006)要区别对待:前者不重连,后者立即进重连流程 - 读循环外层必须用
for { select { case ,否则 goroutine 泄漏
重连不能写在 ReadMessage 错误分支里硬循环
在 ReadMessage 出错后直接 for { dial(); time.Sleep() } 是典型反模式。它卡死当前 goroutine,无法响应外部停止指令,且掩盖真实错误(比如 DNS 解析失败、证书过期)。正确做法是把重连抽成独立 goroutine:
- 用
context.WithCancel(parentCtx)创建子 ctx,重连 goroutine 监听ctx.Done() - 每次重连前先
if conn != nil { conn.Close() },否则旧连接 fd 不释放 - 重连成功后,重新启动读/写 goroutine,并调用
conn.SetPingHandler()和conn.SetWriteDeadline() - 写消息 channel 必须替换为新连接专用的,否则往已关闭的 channel 发数据会 panic
WriteMessage 并发不安全,必须串行化写操作
*websocket.Conn.WriteMessage() 不是并发安全的。多个 goroutine 同时调用会导致 panic 或消息错乱。生产环境唯一靠谱方案是:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 起一个专属写 goroutine,监听
chan []byte或chan Message - 所有业务逻辑通过 channel 发送消息,写 goroutine 拿到后统一调
conn.WriteMessage() - 避免用
sync.Mutex包裹写操作——高吞吐下会成为瓶颈,且重连后锁状态难清理 - 注意:重连后必须关闭旧 channel 并新建一个,否则新连接还在往已关闭的 channel 写,会触发
send on closed channel
心跳不是可选项,而是保活刚需
没心跳,NAT、LB、防火墙大概率 60–90 秒就静默 kill 连接。客户端必须主动发 ping:
- 用
conn.SetPingHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)) })处理 pong - 另起 goroutine 定期调
conn.WriteMessage(websocket.PingMessage, nil),间隔建议 25–30s - 写 ping 前必须设
conn.SetWriteDeadline(),否则网络卡住时会永久阻塞 - 心跳 goroutine 也要监听
ctx.Done(),重连后需重启,否则旧 timer 可能往已关闭 conn 写 ping
最易被忽略的是:重连后所有状态都要重置——不只是 conn 和 channel,还有心跳 timer、读写 deadline、订阅关系、甚至自定义的 session ID。漏掉任意一项,都可能导致后续通信静默失败,日志里却找不到明显报错。

















