直接回答:半开连接:一端已经不在(宕机、断电、进程崩溃没来及挥手),另一端还以为连接活着——因为 TCP 没有流量时线路完全静默,对端崩溃不会产生任何报文。不处理的话连接资源永久泄漏、消息发进黑洞。检测手段两类:TCP keepalive(内核级,默认 2 小时才探,几乎无用)与应用层心跳(秒级,自己实现)。
展开解析:TCP keepalive 的细节与局限:SO_KEEPALIVE 开启后,空闲 tcp_keepalive_time(默认 7200 秒)开始每 keepalive_intvl(75s)探 keepalive_cnt(9)次,全失败才断——默认参数下发现死连接要两小时以上,调小参数影响全系统且 NAT/防火墙的空闲超时(常 5~15 分钟)照样把你掐了,所以它只能兜底。应用层心跳是正道:协议设计 ping/pong 帧(WebSocket 自带、gRPC 的 HTTP/2 PING),间隔 30~60 秒,连续 N 次未回判死重连;服务端要设读超时兜底(客户端不发心跳也得踢)。NAT 空闲超时的应对:心跳间隔必须小于路径上最短 NAT 超时(移动网络激进,60s 是安全线),长连接服务(IM、推送)的心跳频率是省电与保活的平衡——Android 厂商杀后台让问题更复杂,才有厂商通道的兴起。相关概念区分:半开(half-open,一端死)与半关闭(half-close,一方 FIN 但还能收,TCP 的合法状态)别混;TIME_WAIT 是正常关闭的尾巴与半开无关。工程清单:连接池(数据库、Redis)必须配 testOnBorrow 或 idle 探活——“从池里拿到死连接”是半开问题的日常形态;LB 的 idle timeout 与后端 keepalive 要对齐,否则 LB 先掐后端还在等。
追问方向:为什么崩溃端重连后旧连接还存在(服务端视角)?gRPC 的 max_connection_idle 与心跳如何配合?
(约 500 字)