readmessage错误即断连信号,需用websocket.isunexpectedcloseerror区分是否重连;重连应独立goroutine管理,配合context控制;写操作须串行化并替换channel;心跳为保活刚需,需应用层实现。

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 { ... }},否则 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 或消息错乱。生产环境唯一靠谱方案是:
- 起一个专属写 goroutine,监听
chan []byte或自定义Message类型 channel - 所有业务逻辑通过 channel 发送消息,写 goroutine 拿到后统一调
conn.WriteMessage() - 避免用
sync.Mutex包裹写操作——高吞吐下会成为瓶颈,且重连后锁状态难清理 - 重连后必须关闭旧 channel 并新建一个,否则新连接还在往已关闭的 channel 写,会触发
send on closed channel
心跳不是可选项,而是保活刚需
仅靠 TCP Keep-Alive 不够,NAT 设备或 LB 通常 60s 左右静默 kill 空闲连接。必须在应用层实现心跳:
- 设置
conn.SetPongHandler(),收到 pong 时重置SetReadDeadline() - 另起 goroutine 定期调
conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(10 * time.Second)) - 心跳间隔建议 30~45s,读超时设为 60s,避免误判
-
websocket.DefaultDialer缺省不配KeepAlive和WriteDeadline,需手动配置http.Transport和conn.SetWriteDeadline()
重连逻辑最易被忽略的点在于:资源清理与状态同步不同步。比如旧连接的 timer 还在往已关闭的 conn 发 ping,旧 channel 还在接收业务消息却无人消费,或者重连后没重置订阅状态导致消息重复或丢失。这些不是“写完就跑”的代码能覆盖的,必须把连接生命周期当成一个有始有终的状态机来管。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











