Gin长连接需四层协同优化:设IdleTimeout小于中间设备超时(如55秒),用net.ListenConfig开启TCP keepalive(KEEPIDLE=30s等),WebSocket/SSE加应用层心跳,Connection头无效。

Gin 本身不控制长连接生命周期,所谓“优化”其实是配置底层 http.Server 和监听器,否则无论中间件写得多漂亮,连接照样被 NAT、SLB 或反向代理在空闲时无声断开。
设置 http.Server.IdleTimeout 必须小于中间设备超时
这是最容易被忽略的硬性约束。云厂商负载均衡(如阿里云 SLB、腾讯云 CLB)默认清理空闲连接的时间是 60–300 秒;Nginx 的 keepalive_timeout 默认是 65 秒。如果你把 IdleTimeout 设成 90 秒,服务端还在等,连接早被中间层干掉了。
-
IdleTimeout必须显式设置,Go 1.22+ 默认值是 0(即无限),这在生产环境极其危险 - 建议设为
55 * time.Second,比 Nginx 的 65 秒小 10 秒,留出缓冲余量 -
ReadTimeout和WriteTimeout不影响保活,但设为 0 可能导致慢请求拖垮整个连接池;建议统一设为10 * time.Second防异常阻塞
启用 TCP 层 SO_KEEPALIVE 探测必须绕过 http.ListenAndServe
用 http.ListenAndServe(":8080", r) 启动,等于放弃了对底层 socket 的控制权——你没法在连接建立前就设置 SO_KEEPALIVE、TCP_KEEPIDLE 这些关键选项。Linux 默认 2 小时才发第一个探测包,远超云厂商 5 分钟阈值。
- 必须用
net.ListenConfig构造 listener,并在Control回调里调用syscall.SetsockoptInt - 关键参数:开启
SO_KEEPALIVE,设TCP_KEEPIDLE=30(空闲 30 秒发首探)、TCP_KEEPINTVL=10(每 10 秒重发)、TCP_KEEPCNT=3(3 次失败后断连) - 这段代码必须放在
gin.Engine.Run()之前,且不能和http.ListenAndServe混用
WebSocket/SSE 场景下必须补充应用层心跳
TCP keepalive 解决的是网络链路层断连问题,但很多代理(比如某些版本的 Nginx)会主动终止 WebSocket 连接,哪怕 TCP 层还活着。这时候只靠系统级探测不够,得在业务协议里加心跳。
- 不要依赖客户端单向 ping;服务端应定时向每个活跃连接发
Ping帧(WebSocket)或data:行(SSE) - gorilla/websocket 的
SetPingHandler+SetPongHandler要配对使用,否则可能因未响应 pong 导致连接被关 - 心跳间隔建议 ≤ 25 秒,避开大多数代理的默认超时窗口(如 Nginx 的
proxy_read_timeout默认 60 秒)
Connection: keep-alive 头完全没用,别再写了
在 handler 里写 c.Writer.Header().Set("Connection", "keep-alive") 是典型误区。HTTP/1.1 默认就是 keep-alive,这个头只是告诉客户端“可以复用”,但服务端没配 IdleTimeout、没开 TCP keepalive、没设应用层心跳,它撑不过 60 秒。
- 这个 Header 对连接存活零影响,纯属心理安慰
- 真正起作用的是
http.Server实例的字段配置 + socket 系统选项 + 应用协议设计 - 如果用了反向代理,还要同步检查它的超时设置(如 Nginx 的
proxy_http_version 1.1和proxy_set_header Connection "")
长连接不是“开了就行”,而是四层协同:TCP 层探测、HTTP 层超时、代理层对齐、应用层兜底。少一层,线上就容易出现“连接突然中断但日志无报错”的静默故障。



















