登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

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;因此“没有收到错误”最常见的原因,是没有调用读方法,或忽略了返回值。写端能把一段数据交给内核缓冲区,并不等于它已经知道对端关闭。

Go Gorilla WebSocket 读循环处理数据帧、Close 控制帧与 CloseError 的静态结构说明图
图1:Go WebSocket 读循环与关闭控制帧的关系说明图。

服务端应保持一个明确的读循环,并把退出动作集中到同一个位置:

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 仍会返回关闭错误。一个连接只保留一个读协程,写协程也要单独串行化,避免多个读者同时修改连接状态。

Go WebSocket CloseError、正常关闭、异常断开与读超时边界的静态关系图
图2:正常关闭、异常断开和网络静默的错误边界说明图。
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 和读超时分开记录,通常就能解释为什么连接已经断开,却没有在写代码的地方立即看到错误。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>