websocket连接异常关闭时返回closeabnormalclosure,是tcp断连的协议层兜底;主动调用writemessage发关闭帧才得closenormalclosure;读出错后须立即检查iscloseerror并安全处理,不可defer close或继续读写。

WebSocket连接关闭时为什么总收到websocket.CloseAbnormalClosure
Go 的 gorilla/websocket 包里,只要底层 TCP 连接断了(比如客户端直接关浏览器、网络中断、Nginx 代理超时),服务端读消息时几乎必然触发 websocket.CloseAbnormalClosure —— 它不是你代码写错了,而是协议层的“默认兜底”。真正由你主动调用 conn.WriteMessage(websocket.CloseMessage, ...) 关闭的,才会返回 websocket.CloseNormalClosure。
常见错误现象:read: connection closed 或 use of closed network connection 跟着一个 websocket.CloseAbnormalClosure 一起出现,说明读操作已经失败,再试图 WriteMessage 就会 panic。
- 必须在
err != nil后立刻检查websocket.IsCloseError(err, ...),而不是直接return或继续写 - 不要在
defer conn.Close()里指望它能“安全收尾”:如果连接已断,Close()本身可能 panic - 若需发关闭帧,必须在读出错前、且连接还可用时调用
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(...))
如何正确响应客户端发来的CloseMessage
客户端主动关闭时,会先发一个 CloseMessage 帧,服务端要读到它,再回一个同类型的帧,才算标准握手。但 conn.ReadMessage() 不会把 CloseMessage 当成普通消息返回,而是直接返回 err 并设为 websocket.CloseMessage 类型的错误。
- 用
websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway)判断是否为合法关闭请求 - 判断成立后,立即调用
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - 写完就
return,别再读或写其他消息,否则可能触发use of closed network connection - 注意:
FormatCloseMessage第二个参数是原因字符串,长度不能超过 123 字节,超长会被截断或报错
conn.SetReadDeadline 和 conn.SetWriteDeadline 怎么配
WebSocket 连接长期空闲时,中间设备(如负载均衡、防火墙)常会静默断连。靠心跳保活是必须的,而 deadline 是心跳机制的基础设施。但读写 deadline 必须分开设,且不能只设一个。
立即学习“go语言免费学习笔记(深入)”;
- 读 deadline 应略大于心跳间隔(例如心跳 30s,读 deadline 设 35s),否则心跳包还没来就读超时,误判为断连
- 写 deadline 要比单次写操作预期耗时稍长(比如发一条消息通常
- 每次成功
ReadMessage或WriteMessage后,都得重置对应方向的 deadline,否则过期后下一次操作直接报i/o timeout - 不要在
for循环外一次性设置 deadline:它是一次性的,超时后需手动重置
为什么defer conn.Close() 在 goroutine 里容易失效
典型写法是起一个 goroutine 处理读,另一个处理写。如果读 goroutine 因错误退出,defer conn.Close() 只在该 goroutine 结束时触发,但写 goroutine 可能还在跑,继续往已关闭的连接写数据,就会 panic。
- 不要依赖单个 goroutine 的
defer关闭连接,改用显式状态控制(如sync.Once+close(doneCh)) - 所有读写操作前,先 select 检查
doneCh是否已关闭,避免竞态 -
conn.Close()本身不是并发安全的,多个 goroutine 同时调用会 panic;确保全局只有一次调用机会 - 关闭连接后,记得关闭关联的 channel(如消息发送 channel)、取消 context,防止 goroutine 泄漏
最麻烦的点其实是:关闭帧的发送时机和错误类型的交叉判断——IsCloseError 返回 true 时,连接未必还能写;而 IsUnexpectedCloseError 为 true 时,往往已经来不及发任何帧了。这点不细看文档很容易漏掉。










