必须显式配置 ping/pong 处理与动态读超时刷新,并对齐 nginx 等中间件 timeout(如 proxy_read_timeout ≥ 75s),否则连接会在约 60 秒被静默切断。

只设 SetKeepAlive(true) 或只依赖浏览器自动 ping/pong,连接大概率会在 60 秒左右被 Nginx 或防火墙静默切断——这不是代码 bug,是配置和机制没对齐。
gorilla/websocket 必须显式启用 Ping/Pong 处理
标准库 net/http 和原生 websocket 包都不处理控制帧;gorilla/websocket 默认也不会自动响应 Ping,除非你手动注册处理器:
-
conn.SetPingHandler()必须设置,否则客户端发来的 Ping 被丢弃,Nginx 会因超时主动断连 -
conn.SetPongHandler()推荐设,用于重置读超时(不是必须,但不设就无法感知 Pong 到达) - 别在
SetPingHandler里做耗时操作(如写日志、查 DB),它运行在读协程中,一卡全卡住
WriteControl 发 Ping 必须配超时 + 独立 goroutine
用 WriteControl(websocket.PingMessage, nil, ...) 发心跳,不能用 WriteMessage(后者不支持控制帧):
- 必须传入写 deadline,比如
time.Now().Add(5 * time.Second),否则底层 write buffer 满时 goroutine 会永久阻塞 - 必须在独立 goroutine 中跑
time.Ticker,不能塞进读循环——否则读消息卡住时,心跳就停摆 - 推荐间隔:25 秒(小于 Nginx 默认
proxy_read_timeout=60s,留出安全余量)
读超时不是设一次就完事,要动态刷新
SetReadDeadline 是绝对时间,每次读操作前都得重设,否则超时后下一次 ReadMessage 直接返回 error:
- 在
SetPongHandler里调conn.SetReadDeadline(time.Now().Add(60 * time.Second)),表示“收到 Pong 就算活跃” - 每次成功
ReadMessage后也得立刻重设,覆盖业务消息带来的活跃信号 - 别设成固定值(如启动时设死 60 秒后),否则空闲期间超时照常触发
Nginx 和负载均衡器的 timeout 比代码更关键
服务端心跳再稳,只要中间件先动手,连接照样断:
-
proxy_read_timeout必须 > 服务端最大空闲窗口(建议 ≥ 75s) - 加
proxy_set_header Connection '',防止 Nginx 复用连接干扰 Upgrade 流程 - ALB/SLB 等云负载均衡器也有 idle timeout,默认常为 60s,需单独调整
真正容易被忽略的,是读超时的刷新时机和中间件 timeout 的数值对齐——这两处一错,心跳逻辑写得再漂亮也白搭。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











