Gorilla WebSocket连接建立约1分钟后出现Read TCP I/O超时问题
解决WebSocket连接1分钟IO超时断开问题
你遇到的read tcp ... i/o timeout错误,本质是空闲TCP连接被中间网络设备(防火墙、负载均衡等)主动断开——这类设备通常默认1分钟的空闲超时,当WebSocket连接超过1分钟没有数据传输时,就会被切断。你的当前代码只在被动等待客户端发消息,没有维持连接活跃的机制,所以触发了超时。
解决方案:添加WebSocket心跳机制
通过服务器定期发送Ping帧、客户端自动回复Pong帧的方式,让连接保持活跃,避免被中间设备判定为空闲断开。
修改后的代码示例:
import "time" func (c *YourConnectionType) handleWebSocket() { // 1. 设置读超时,并注册Pong处理函数(收到Pong后重置超时) err := c.RawConn.SetReadDeadline(time.Now().Add(30 * time.Second)) if err != nil { c.logger.Errorf("set read deadline for %s failed: %v", c.connectionID, err) return } c.RawConn.SetPongHandler(func(appData string) error { // 收到客户端的Pong回复,重置读超时 return c.RawConn.SetReadDeadline(time.Now().Add(30 * time.Second)) }) // 2. 启动goroutine定期发送Ping帧(间隔要小于读超时时间) pingTicker := time.NewTicker(25 * time.Second) defer pingTicker.Stop() go func() { for { select { case <-pingTicker.C: // 发送Ping帧给客户端 if err := c.RawConn.WriteMessage(websocket.PingMessage, nil); err != nil { c.logger.Errorf("send ping to %s failed: %v", c.connectionID, err) return } case <-c.ctx.Done(): // 连接上下文结束,退出goroutine return } } }() // 3. 原有的消息读取循环 for { messageType, message, err := c.RawConn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { c.logger.Errorf("websocket unexpected close from %s: %v", c.connectionID, err) } else { c.logger.Debugf("websocket connection closed from %s: %v", c.connectionID, err) } break } fmt.Printf("Received message: %s\n", message) } }
关键逻辑说明:
- 读超时设置:把读超时设为30秒,确保在连接真的异常时能及时检测到。
- Ping/Pong机制:每25秒发送一次Ping帧(间隔小于读超时),客户端收到后会自动回复Pong(浏览器原生WebSocket默认支持),服务器收到Pong就重置读超时,让连接一直处于活跃状态。
- 异常处理:如果客户端长时间没回复Pong,读超时会触发,服务器就能捕获到错误并断开连接,避免无效连接占用资源。
额外注意事项
- 若使用自定义JavaScript客户端而非浏览器原生WebSocket,需要确保客户端实现了Pong响应逻辑,否则服务器的Ping无法得到回复,依然会触发超时。
- 如果有权限调整部署环境的防火墙/负载均衡配置,可以适当延长空闲超时时间,但这只是辅助手段,心跳机制才是跨环境通用的可靠解决方案。
内容的提问来源于stack exchange,提问作者Andrew Cui
相关产品推荐
相关产品推荐

