TCP 连接空闲时不会自己发现对端已经没了。keepalive 就是内核替你定期问一句“你还在吗”。这篇在 macOS 上看默认参数有多保守、探测包长什么样、连续几次没回应才判死、公网服务器会不会回应探测。其中“对端真的消失”这一步造不出真黑洞,我用 50% 丢包替代,并会写明。
net.inet.tcp.keepidle: 7200000 net.inet.tcp.keepintvl: 75000 net.inet.tcp.keepcnt: 8 net.inet.tcp.always_keepalive: 0 单位:keepidle、keepintvl 是毫秒。默认:空闲 7200000 ms = 2 小时才发第一个探测,之后每 75 s 一个,8 次无回应才判死 按默认值,一条空闲连接的对端消失后,最长要 7200 + 75 × 8 = 7800 秒(2.17 小时)才会报错(公式计算,下面第四节用缩短的参数实测)
keepalive 默认关闭;打开后空闲 7200 秒才探测、间隔 75 秒、8 次判死。这三个参数可以用 TCP_KEEPALIVE、TCP_KEEPINTVL、TCP_KEEPCNT 针对单个套接字修改。
新连接上 SO_KEEPALIVE = 0 socket 模块的常量:TCP_KEEPALIVE=True(值 16),TCP_KEEPINTVL=True(值 257),TCP_KEEPCNT=True(值 258),TCP_KEEPIDLE=False 不设置时读回:TCP_KEEPALIVE=7200 TCP_KEEPINTVL=75 TCP_KEEPCNT=8 设成 (idle=2, intvl=1, cnt=3) 后读回:SO_KEEPALIVE=8 TCP_KEEPALIVE=2 TCP_KEEPINTVL=1 TCP_KEEPCNT=3
macOS 的名字是 TCP_KEEPALIVE(相当于 Linux 的 TCP_KEEPIDLE);SO_KEEPALIVE 读回的是内部标志位 8,不是 1。
9 秒内线上共 8 个包(设置选项前连接已有的数据已不在内):
+ 1.79 s 客户端→服务端 载荷 0 字节 seq(相对)=10576 ack(相对)=18968 标志 0x10
+ 1.79 s 服务端→客户端 载荷 0 字节 seq(相对)=18968 ack(相对)=10577 标志 0x10
+ 3.79 s 客户端→服务端 载荷 0 字节 seq(相对)=10576 ack(相对)=18968 标志 0x10
+ 3.79 s 服务端→客户端 载荷 0 字节 seq(相对)=18968 ack(相对)=10577 标志 0x10
+ 5.79 s 客户端→服务端 载荷 0 字节 seq(相对)=10576 ack(相对)=18968 标志 0x10
+ 5.79 s 服务端→客户端 载荷 0 字节 seq(相对)=18968 ack(相对)=10577 标志 0x10
+ 7.79 s 客户端→服务端 载荷 0 字节 seq(相对)=10576 ack(相对)=18968 标志 0x10
+ 7.79 s 服务端→客户端 载荷 0 字节 seq(相对)=18968 ack(相对)=10577 标志 0x10
探测包个数 4,间隔(s):[2.0, 2.0, 2.0]0 字节载荷的纯 ACK,序号比对端期望的小 1,对端回一个纯 ACK。对端一直回应时,探测每个 idle 周期一个。
25 秒内被内核判死的连接:30 / 30;其余 0 条一直有探测得到回应(在丢包下运气好),仍然连着 判死的时间(秒,从放进丢包环境算起):最短 5.0 / 中位 7.5 / 最长 22.0;recv 的错误:['ETIMEDOUT Operation timed out'] 规则下限:idle + intvl × cnt = 2 + 1 × 3 = 5 秒(第一个探测在空闲 2 秒后,之后每 1 秒一个,连续 3 次探测都没有回应,再过 1 个间隔才判死,具体边界以最短值为准)
连续 cnt 次没有回应才断开,recv 得到 ETIMEDOUT;最短时间等于 idle + intvl × cnt。注意本篇用 50% 丢包代替黑洞。
+ -0.23 s 本机→服务器 载荷 0 字节 标志 0xc2
+ -0.00 s 服务器→本机 载荷 0 字节 标志 0x52
+ -0.00 s 本机→服务器 载荷 0 字节 标志 0x10
+ 2.00 s 本机→服务器 载荷 0 字节 标志 0x10
+ 2.23 s 服务器→本机 载荷 0 字节 标志 0x10
+ 4.23 s 本机→服务器 载荷 0 字节 标志 0x10
+ 4.46 s 服务器→本机 载荷 0 字节 标志 0x10
+ 6.46 s 本机→服务器 载荷 0 字节 标志 0x10
+ 6.69 s 服务器→本机 载荷 0 字节 标志 0x10
握手之后(第 1 秒起)本机发出的探测 3 个,服务器回的包 3 个;相邻探测的间隔(s):[2.23, 2.23]
探测到回应的延迟(ms):[227, 226, 230]example.com 会回应探测;下一个探测周期是从收到回应开始数的,所以间隔是 idle 加一个 RTT。