readmessage 错误即断连信号,需用 websocket.isunexpectedcloseerror 区分重连;writemessage 非并发安全,须单 goroutine 串行写;重连应独立 goroutine + context 控制;心跳为保活刚需,需手动设置 ping/pong 及超时。

ReadMessage 错误就是断连信号,别等超时
gorilla/websocket 的 conn.ReadMessage() 一出错,基本就是连接断了。它不会阻塞重试,也不会自动恢复——错误返回即事实断连。常见错误包括:*websocket.CloseError(含 Code 字段)、io.EOF、net.OpError(比如 dial tcp: i/o timeout)。不能只写 if err != nil { return } 就退出读循环,必须区分是否真要重连:
- 用
websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway)判断是否该重试 -
websocket.CloseNormalClosure(1000)是主动关闭,不重连 -
websocket.CloseAbnormalClosure(1006)是异常断开,应立即进重连流程
WriteMessage 并发不安全,必须串行化写操作
*websocket.Conn.WriteMessage() 不是并发安全的。多个 goroutine 同时调用,轻则消息错乱,重则 panic。生产环境唯一靠谱方案是起一个专属写 goroutine,监听专用 channel:
- 所有业务逻辑往
writeCh chan []byte发消息,写 goroutine 从 channel 取、统一调conn.WriteMessage() - 重连后必须关闭旧
writeCh并新建一个,否则往已关闭的 channel send 会触发send on closed channel - 别用
sync.Mutex包裹写操作——高吞吐下成瓶颈,且重连后锁状态难清理
重连不能写在 ReadMessage 错误分支里硬循环
在 ReadMessage 出错后直接写 for { dial(); time.Sleep() } 是典型反模式。它卡死当前 goroutine,无法响应外部停止指令,还掩盖真实错误(比如 DNS 解析失败、证书过期):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 正确做法:把重连抽成独立 goroutine,用
context.WithCancel(parentCtx)创建子 ctx,重连 goroutine 监听ctx.Done() - 每次重连前先
if conn != nil { conn.Close() },否则旧连接 fd 不释放,Linux 下很快 hitulimit - 重连成功后,重新启动读/写 goroutine,并调用
conn.SetPingHandler()和conn.SetWriteDeadline()
心跳不是可选项,而是保活刚需
NAT 中间件(家用路由器、云 LB)通常 60s 左右静默 kill 空闲连接。gorilla/websocket 默认配置没设 KeepAlive 和 WriteDeadline,必须手动补全:
- 启动一个 goroutine 定期调
conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second)) - 注册
conn.SetPongHandler(),收到 Pong 后重置SetReadDeadline - 心跳间隔建议 30~45 秒,读取超时设为 60 秒,退避上限 30 秒,最大重试次数建议 10 次
重连后必须同步重置心跳计时器,否则旧 ticker 可能还在往已关闭的 conn 写 ping。










