
Go 的 net.Conn.SetDeadline 和 SetReadDeadline 怎么配才不丢连接
Go 默认不设超时,TCP 连接卡住会一直 hang 住 goroutine。用 SetDeadline 是最直接的解法,但容易误以为“设一次就万事大吉”。
常见错误是只在建立连接后调用一次 conn.SetDeadline,结果后续读写仍无限期等待 —— 它是一次性的,每次 Read 或 Write 前都得重设。
-
SetReadDeadline只影响下一次Read,不是“从此之后所有读都带超时” - 写操作要用
SetWriteDeadline单独设,读写超时可不同 - 如果用
bufio.Reader包装连接,超时必须设在底层conn上,bufio不转发 deadline - HTTP server 默认不启用连接级读超时,需通过
http.Server.ReadTimeout配置,不是靠 conn 方法
为什么 SetKeepAlive 开了还是被中间设备断连
Go 的 SetKeepAlive(true) 只打开 TCP 层 keepalive 开关,但默认参数极不友好:Linux 内核默认 2 小时才发第一个探测包,远超大多数 NAT 网关或防火墙的 5–30 分钟空闲清理阈值。
光开开关没用,必须调参。Go 1.19+ 才支持 SetKeepAlivePeriod,老版本只能靠系统级调优或自己发应用层心跳。
立即学习“go语言免费学习笔记(深入)”;
- Go 1.19+:用
conn.(*net.TCPConn).SetKeepAlivePeriod(30 * time.Second)控制探测间隔 - 低于 1.19:只能调宿主机
/proc/sys/net/ipv4/tcp_keepalive_time,无法 per-connection 控制 - KeepAlive 是 TCP 层机制,对已 RST 或静默丢包的链路无效;它只探测“对方是否还在”,不保证应用层可达
- 某些云厂商 LB(如 AWS ALB、阿里云 SLB)会主动终止空闲连接,且不透传 keepalive 包,此时必须依赖应用层心跳
应用层心跳怎么写才不和 Read 冲突
当连接要长期存活(比如 WebSocket、MQTT、自定义协议),纯 TCP keepalive 不够用,必须自己发心跳包。但直接起 goroutine 循环 Write + Read 极易和业务读逻辑抢 conn,导致 read: connection reset by peer 或阻塞。
- 心跳
Write必须配SetWriteDeadline,否则网络卡顿时整个心跳 goroutine 挂死 - 心跳
Read不能用阻塞式,建议用SetReadDeadline+conn.Read轮询,或改用conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))短超时非阻塞尝试 - 避免在心跳 goroutine 里直接处理业务数据;心跳和业务读应共享同一
conn,但读逻辑需识别心跳帧(比如固定前缀"PING")并跳过 - 如果用
bufio.Reader,心跳响应可能被 buffer 滞留,导致业务Read读不到新数据 —— 此时要么不用bufio,要么用Peek+Discard清理缓冲区
net/http 里的 KeepAlive 配置到底管谁
很多人看到 http.Transport.MaxIdleConnsPerHost 或 Server.IdleTimeout 就以为是控制 TCP keepalive,其实完全不是一回事。
这些是 HTTP/1.1 连接复用层面的 idle 管理,跟 TCP 层的 SO_KEEPALIVE 无关。它们决定的是“HTTP 连接空闲多久后主动关闭”,不发任何 TCP 探测包。
-
http.Transport.IdleConnTimeout:控制 client 复用连接的最大空闲时间,超时后连接被从 pool 移除并关闭 -
http.Server.IdleTimeout:server 端接受到请求后,若连接空闲超过该时间,server 主动断开 - 这两个 timeout 触发的是优雅关闭(FIN),不是 keepalive 探测;它们对长连接保活无帮助,反而可能提前断掉本可继续用的连接
- 真要保活 HTTP 长连接(如 Server-Sent Events),必须配合应用层心跳,或降级为 HTTP/2(自带流级 keepalive)
KeepAlive 参数分散在 TCP 连接、HTTP transport/server、甚至操作系统里,混用时容易互相覆盖。最稳的路径是:明确区分“连接是否还通”(用 TCP keepalive 或应用心跳)和“连接是否还能复用”(用 HTTP idle timeout),别指望一个配置解决所有问题。










