Go WebSocket 断开后为什么没有收到错误
来源:17golang原创
时间:2026-10-23 02:22:58 338浏览 收藏
Go WebSocket 断开后没有马上看到错误,通常不是连接“不会报错”,而是程序没有持续读取连接。以 github.com/gorilla/websocket 为例,关闭控制帧、ping/pong 和数据帧都要经过读方法处理;如果代码只负责写消息,或者读循环已经退出,关闭信号就不会在你期待的位置出现。
官方地址:https://github.com/gorilla/websocket/
- Close 帧通常在
ReadMessage、NextReader或消息读取器的Read中体现为*websocket.CloseError。 - 对端正常离开与 TCP 断开不是同一种结果,不能只判断一个字符串。
- 长期没有任何帧时,必须配合读超时或心跳判断网络是否静默。
先分清关闭信号和读循环
Gorilla WebSocket 的关闭处理器是在读方法内部调用的。收到 Close 帧时,默认处理器会回发关闭帧,随后读方法返回 CloseError;因此“没有收到错误”最常见的原因,是没有调用读方法,或忽略了返回值。写端能把一段数据交给内核缓冲区,并不等于它已经知道对端关闭。

服务端应保持一个明确的读循环,并把退出动作集中到同一个位置:
func readLoop(c *websocket.Conn) {
defer c.Close() // 读循环退出后释放底层连接
for {
_, _, err := c.ReadMessage() // 只有读路径会处理 Close、Ping 和 Pong
if err != nil {
var closeErr *websocket.CloseError
if errors.As(err, &closeErr) {
// 关闭码和文本比直接打印 err 更适合做业务分类
log.Printf("websocket closed: code=%d text=%q", closeErr.Code, closeErr.Text)
} else {
// EOF、网络错误或读超时都从这里进入统一收尾
log.Printf("websocket read failed: %v", err)
}
break // 读方法出错后不要继续复用这个连接
}
}
}
上例需要导入 errors、log 和 github.com/gorilla/websocket。CloseNormalClosure、CloseGoingAway 等正常关闭码可以作为业务日志中的“正常离开”,但也不要把所有非正常码都当成服务故障:客户端刷新、代理回收连接和网络切换都可能改变关闭路径。
让 ReadMessage 成为唯一的连接状态观察点
如果需要在关闭帧到达时记录更多上下文,可以安装 SetCloseHandler。它适合做日志、指标或连接表清理,但不应替代读循环;处理器执行后,ReadMessage 仍会返回关闭错误。一个连接只保留一个读协程,写协程也要单独串行化,避免多个读者同时修改连接状态。

c.SetCloseHandler(func(code int, text string) error {
// 这里记录协议层关闭原因,不在处理器里启动第二个读循环
log.Printf("peer close frame: code=%d text=%q", code, text)
return nil // 返回 nil 后交给默认读循环完成收尾
})
c.SetReadDeadline(time.Now().Add(60 * time.Second)) // 没有任何帧时触发读超时
c.SetPongHandler(func(string) error {
// 收到 Pong 说明链路仍有活动,续期下一次读等待
return c.SetReadDeadline(time.Now().Add(60 * time.Second))
})
如果对端发送的是规范 Close 帧,通常能拿到 CloseError;如果对端直接断开 TCP,可能看到 io.EOF、unexpected EOF 或底层网络错误;如果连接一直没有帧,最终可能是 net.Error 的超时。诊断时应同时记录关闭码、错误类型、远端地址、最后一次收发时间和连接 ID,不能只看“有没有 error”。
| 现象 | 优先检查 | 处理方式 |
|---|---|---|
| 对端发送 Close 后无日志 | 是否仍有 ReadMessage/NextReader | 恢复唯一读循环并记录返回值 |
| 读到 CloseError | 关闭码与文本 | 区分正常离开和协议/业务异常 |
| 长时间无任何错误 | 读超时与心跳 | 设置 deadline,Pong 后续期 |
| 偶发 EOF 或 reset | 代理、客户端网络、服务重启 | 记录上下文后按重连策略处理 |
常见问题
只调用 c.Close() 能收到对端的关闭原因吗?
不能。c.Close()直接关闭底层网络连接,不会等待对端 Close 帧;要读取关闭原因,必须让读方法运行。
为什么写消息成功但下一刻连接就失效?
写成功可能只代表数据进入本地发送路径,关闭状态要到下一次读或后续写入时才暴露。读循环和写错误都应分别记录。
SetCloseHandler 返回错误会怎样?
返回值会影响读方法的处理结果。若只是记录关闭信息,通常返回 nil,并让外层读循环统一结束和释放资源。
排查这类问题时,先确认“谁在读、读到哪里、错误是什么类型”,再讨论重连或心跳参数。把关闭帧、网络 EOF 和读超时分开记录,通常就能解释为什么连接已经断开,却没有在写代码的地方立即看到错误。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习