Go的http.Server默认不启用TCP keepalive,因OS默认2小时探测远超NAT/防火墙5–30分钟清理阈值,导致连接被静默断开;必须通过net.ListenConfig设置KeepAlive并协同配置ReadTimeout、WriteTimeout和IdleTimeout(须小于中间设备超时)才能真正保活。

Go 的 http.Server 默认不启用 TCP keepalive,长连接空闲时会被中间网络设备(如 NAT、防火墙)静默断开,必须手动配置底层 net.Listener 和 http.Server 的超时参数才能真正保活。
为什么默认 HTTP 长连接会悄无声息断开
Go 的 http.Server 本身不控制 TCP 层的 keepalive 行为,只管应用层读写。操作系统默认的 TCP keepalive 周期通常是 2 小时(Linux /proc/sys/net/ipv4/tcp_keepalive_time),远超多数中间设备的空闲清理阈值(常见 5–30 分钟)。结果就是连接在服务端还“活着”,客户端发请求时直接收到 connection reset by peer 或 broken pipe。
如何给 Listener 启用并调优 TCP keepalive
必须在创建监听器时显式设置 net.ListenConfig,不能依赖 http.ListenAndServe 的默认行为:
lc := net.ListenConfig{
KeepAlive: 30 * time.Second, // 触发 keepalive 探测的空闲时间
}
ln, err := lc.Listen(context.Background(), "tcp", ":8080")
if err != nil {
log.Fatal(err)
}
server := &http.Server{Handler: mux}
server.Serve(ln)
-
KeepAlive > 0才真正开启 OS 层 keepalive;设为 0 则继承系统默认(通常无效) - 实际探测间隔由 OS 决定(Linux 下:第一次探测后若无响应,每 75 秒重试,最多 9 次),
KeepAlive只控制“开始探测前的空闲等待” - 注意:Windows 上该字段对应
SO_KEEPALIVE,但不支持细粒度调优,行为更不可控
http.Server 的 ReadTimeout / WriteTimeout 与 IdleTimeout 怎么配
这三个超时不是互斥关系,而是分阶段生效,配错会导致连接提前关闭或无法及时回收:
立即学习“go语言免费学习笔记(深入)”;
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
-
ReadTimeout:从 Accept 连接开始,到读完完整 HTTP 请求头为止(不含 body 流式读取) -
WriteTimeout:从响应开始写入,到写完全部响应数据为止 -
IdleTimeout:连接空闲(即没有新请求)的最大时长 —— 这才是长连接保活的关键约束
典型安全组合(适配 5 分钟 NAT 超时):
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 4 * time.Minute, // 必须小于中间设备超时,留出探测和响应缓冲
}
如果 IdleTimeout ≥ 网络设备超时,连接大概率在下次请求前已被切断,且服务端无感知。
客户端要不要同步配 KeepAlive?
要,而且逻辑对称。Go 客户端默认也不开 TCP keepalive:
tr := &http.Transport{
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
}).DialContext,
IdleConnTimeout: 4 * time.Minute,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{Transport: tr}
关键点:
-
DialContext中的KeepAlive控制新建连接的 TCP keepalive 开关和初始空闲时间 -
IdleConnTimeout控制连接池中空闲连接的最大存活时间,必须 ≤ 服务端IdleTimeout,否则客户端可能复用一个已被服务端关闭的连接 - 如果服务端开了 keepalive 但客户端没开,keepalive 探测包可能被客户端内核丢弃,导致服务端误判连接失效
保活不是单边配置能解决的事,两端的 TCP keepalive、应用层 IdleTimeout、中间网络策略必须对齐。最容易忽略的是把 IdleTimeout 设得比防火墙还长,或者忘记给 net.ListenConfig 设置 KeepAlive —— 这两种情况都会让所有其他配置失效。

















