WebSocket断开时ReadMessage返回1006错误是正常现象,需立即停止读循环、关闭连接并退出goroutine;应主动发CloseMessage、用context协同退出、设读写deadline、用sync.Map安全管理连接及清理。

WebSocket连接断开时 ReadMessage 报 websocket: close 1006 (abnormal closure)
这是最常遇到的“假错误”——连接其实已经断了,但你的代码还在傻等读取。Go 的 gorilla/websocket 库在底层 TCP 断连、客户端强制关闭、网络中断等场景下,会统一返回这个错误,而不是更具体的网络错误。
关键不是“怎么避免它”,而是“怎么正确响应它”:
-
ReadMessage返回该错误时,必须立即停止读循环,关闭连接(如果还没关),并退出 goroutine;继续调用ReadMessage或WriteMessage会 panic 或静默失败 - 不要把它当普通错误 log 后忽略——这会导致 goroutine 泄漏,连接对象无法被 GC
- 服务端主动关闭前,建议先发
CloseMessage:调用conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, "")),再等几毫秒后conn.Close()
为什么 conn.Close() 后还收到 read: connection closed
这不是 bug,是 Go WebSocket 库的竞态现实:你调用了 conn.Close(),但另一端的读 goroutine 可能正卡在系统调用里(比如阻塞在 Read),此时底层 net.Conn 已关,下次 ReadMessage 就会立刻返回该错误。
安全做法是用 channel 或 context 协同退出:
立即学习“go语言免费学习笔记(深入)”;
- 启动读循环时传入
context.Context,每次ReadMessage前检查ctx.Err() != nil - 写操作也应加锁或用
conn.SetWriteDeadline防止无限阻塞 - 不要依赖
defer conn.Close()在函数退出时兜底——goroutine 可能长期存活,得主动管理生命周期
客户端刷新页面或切 Tab 导致的“无声断连”怎么检测
浏览器不会通知服务端它要关连接,TCP 层也可能维持 FIN_WAIT 状态数分钟。服务端靠心跳保活,但别用 SetPingHandler + SetPongHandler 就完事。
真正有效的组合是:
- 服务端调用
conn.SetPingPeriod(30 * time.Second),自动发 ping -
必须同时设置
conn.SetReadDeadline,且 deadline 要大于 ping 间隔(如 45s),否则 pong 回不来就直接报错断连 - 客户端需实现 pong 响应(
websocket.PongHandler默认已注册,但确保没被覆盖) - 不要只靠心跳判断“在线”,业务消息也要更新 lastActive 时间,防止误杀慢客户端
使用 net/http Upgrade 后,HTTP handler 里怎么安全清理资源
很多人把 WebSocket 连接存进 map,但忘了并发读写 map 是 panic 的源头。升级完成后,HTTP handler 不能直接 return,得确保连接生命周期和清理逻辑对齐。
推荐结构:
- 用
sync.Map存连接,key 是用户 ID 或 session token,value 是*websocket.Conn+ 自定义元数据(如登录时间、IP) - 连接断开时,在读/写 goroutine 末尾执行清理:
syncMap.Delete(userID),**不要在 HTTP handler 里删** - 如果需要广播或查在线状态,用
sync.Map.Range遍历,避免锁整个 map - 注意:gorilla 的
Upgrader.Upgrade返回后,原http.ResponseWriter和*http.Request不再可用,别试图往里面写东西
断连处理最麻烦的从来不是代码怎么写,而是状态同步时机——连接对象、内存 map、数据库在线标记、Redis 订阅关系,四者稍有不同步,就会出现“明明断了还收得到消息”或者“用户重连后收不到历史事件”这种问题。










