仅调用SetKeepAlive(true)无法保活TCP长连接,必须在应用层用time.Ticker定时发心跳包并配合SetReadDeadline和SetWriteDeadline控制超时,否则NAT、SLB或家用路由器会在30–300秒内静默回收连接,而Write可能仍成功返回“假活跃”状态。

仅调用 SetKeepAlive(true) 无法保活 TCP 长连接,必须在应用层用 time.Ticker 定时发心跳包,并配合 SetReadDeadline 和 SetWriteDeadline 控制超时——否则 NAT、SLB 或家用路由器会在 30–300 秒内静默回收连接,而你的 Write 还可能成功返回“假活跃”状态。
为什么 SetKeepAlivePeriod 单独用基本无效
Go 1.19+ 支持 conn.(*net.TCPConn).SetKeepAlivePeriod(30 * time.Second),但它只影响内核 TCP 探测行为,实际生效受三重限制:
- Linux 默认首次探测仍需等待 TCP_KEEPIDLE(通常 7200 秒),
SetKeepAlivePeriod只控制后续间隔,不改 idle 时间 - 阿里云 SLB、AWS ALB、家用光猫普遍在 60 秒无流量后直接 kill 连接,根本等不到 TCP 探测包发出
- Windows 上该方法调用成功也不生效,探测周期由系统策略硬性决定
- Wireshark 抓不到 KEEPALIVE 包?大概率是中间设备丢弃了——它不带业务语义,防火墙/代理常过滤
客户端心跳发送必须带写超时 + 固定二进制格式
不能只“发”,要确保“发不出去时能立刻感知并断开”,否则一次网络抖动就让 goroutine hang 住:
- 用
time.NewTicker(25 * time.Second),别用time.AfterFunc:后者易 drift,上一次写阻塞会导致下一次延迟 - 每次
conn.Write前必须调conn.SetWriteDeadline(time.Now().Add(5 * time.Second)),超时即关连接 - 心跳内容用固定长度原始字节,如
[]byte{0x01};避免 JSON、换行符、不定长时间戳——曾有服务因"PING\n"被 Nginx 识别为非法协议直接断连 - 写失败时检查错误类型:
io.EOF、net.ErrClosed、os.SyscallError(含ETIMEDOUT/ECONNRESET)都该立即conn.Close()并退出 goroutine
服务端读超时必须每次 Read 后重置,且不能只靠心跳响应判定存活
很多人设一次 SetReadDeadline 就不管了,结果心跳包刚到,deadline 已过期,下一次 Read 直接报 i/o timeout——这不是网络问题,是逻辑漏设:
立即学习“go语言免费学习笔记(深入)”;
- 无论读到的是业务数据还是心跳包,只要
n, err := conn.Read(buf)中err == nil,就必须立刻重置:conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - 不要单独为心跳开 goroutine 处理:容易与主读循环竞争连接状态;应在主读逻辑里用
bytes.Equal(data[:1], []byte{0x01})判断并跳过业务处理 - 真正断连往往先体现在
Read返回io.EOF或超时;只监听Write错误会误判——比如对方接收缓冲区满,Write成功但后续业务数据发不出 - 连续两次
Read返回net.Error.Timeout()才判定断连,单次抖动不杀连接
goroutine 泄漏与连接清理的边界条件
每个长连接通常启两个 goroutine:一个读循环(处理心跳+业务),一个写循环(发心跳+响应)。连接异常关闭时,它们可能无法自然退出:
- 务必用
sync.Once或atomic.CompareAndSwapInt32保证conn.Close()只执行一次,否则并发 close 会 panic - 读循环中
Read返回非 nil 错误时,应通过 channel 通知写循环退出,并等待其结束再 return -
time.Ticker必须显式ticker.Stop(),否则即使连接关闭,ticker 仍在后台滴答,造成 goroutine 泄漏 - 心跳和业务数据共享同一
conn,解包逻辑必须能区分帧类型;心跳标识应放在固定偏移位置(如首字节),避免粘包干扰判断
最易被忽略的一点:心跳间隔必须比你所知的最短 NAT 超时小至少 10 秒——如果你部署在阿里云 SLB(默认 60 秒回收),心跳就得设成 ≤ 50 秒;如果混用家用宽带和 4G 热点,得按 30 秒设备来设计,否则保活就形同虚设。


















