WebSocket客户端需在读循环中捕获ReadMessage错误并配合心跳判断断连;重连应独立为goroutine,用context控制生命周期、指数退避及conn.Close()清理。

WebSocket客户端怎么判断连接断开了
Go 的 gorilla/websocket 库本身不主动抛出“已断开”事件,连接状态是惰性检测的——你得去读、写或 ping 才会暴露问题。常见现象是 ReadMessage 突然返回 *websocket.CloseError 或 io.EOF,或者 WriteMessage 报 write: broken pipe、use of closed network connection。
真正可靠的判断方式只有两种:
- 在读循环中捕获
ReadMessage返回的错误,并检查是否属于网络层中断(如net.ErrClosed、io.EOF、websocket.IsUnexpectedCloseError) - 配合心跳:用
conn.SetPingHandler+ 定期conn.WriteMessage(websocket.PingMessage, nil),超时未收到 pong 就认为断开
重连逻辑该放在哪里写才不卡住主线程
别在读/写 goroutine 里直接 time.Sleep 后 dial——这会阻塞整个连接生命周期,且无法响应外部停止信号。正确做法是把重连抽成独立 goroutine,用 channel 控制启停。
关键点:
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 用
context.WithCancel管理重连生命周期,避免 goroutine 泄漏 - 每次重连前先
conn.Close()(如果 conn 非 nil),否则旧连接资源不释放 - 指数退避必须做:首次 1s,失败后 2s、4s、8s…上限建议 30s,避免狂刷服务端
- 重连成功后,要重新设置 ping handler、重置读写超时、恢复订阅状态(如有)
示例片段:
go func() {
for {
select {
case <-ctx.Done():
return
default:
}
conn, _, err := dialer.Dial("ws://...", nil)
if err == nil {
// 启动读写 goroutine,退出时 close(done)
return
}
time.Sleep(backoff())
}
}()
gorilla/websocket 的 WriteMessage 并发安全吗
不安全。一个 *websocket.Conn 实例的 WriteMessage 方法不是并发安全的,多个 goroutine 同时调用会 panic 或写乱数据。
解决方案只有两个:
- 用单个写 goroutine + channel 消息队列(推荐):所有业务逻辑往
chan []byte发消息,写 goroutine 从 channel 取、调WriteMessage - 加互斥锁
sync.Mutex,但会串行化写操作,高吞吐下成为瓶颈
注意:重连后必须替换掉旧的写 channel,否则新连接还在往已关闭的 channel 发消息,会 panic。
重连时怎么避免重复收消息或丢消息
WebSocket 协议本身不保证消息顺序或重传,断线期间的消息服务端通常直接丢弃。所以「不丢」只能靠应用层补救:
- 服务端需支持消息 ID + 客户端 ACK 机制,断线重连后客户端带上最后收到的
msg_id,服务端补推后续消息 - 客户端本地缓存未确认的发送消息(比如重要指令),重连成功后重发,配合服务端幂等处理
- 不要依赖 WebSocket 自带的
SetReadDeadline来“保活”,它只影响单次读操作;真正保活靠的是 ping/pong 和连接状态机联动
最容易被忽略的一点:重连成功后,别急着恢复业务逻辑,先等服务端返回一个明确的「重连确认」消息(比如 {"type":"reconnect_ack"}),再清空本地待发队列、重置序列号——否则可能重复提交。

















