关于SO_KEEPALIVE和TCP_USER_TIMEOUT设置的一些误解
SO_KEEPALIVE和TCP_USER_TIMEOUT的设置不合理,导致 程序行为和预期的不一致。
故障现象:

上图对应代码里的超时参数 KEEPIDLE = 60; KEEPINTVAL = 20 ; 并且 TCP_USER_TIMEOUT = 60*1000 ,对应 KEEPIDLE = 60 。
现象上是,16:43:45 时,程序 发出 KEEPALIVE探询包,但是网络上发生了丢包,未收到远端的应答。
结果,16:44:06 时,程序并没有发出第2次KEEPALIVE探询包,而是直接发送了 Reset 。
与编码时的预期不符。
结论 见后面的 小结4 。
小结1:

KEEPIDLE = 20秒;KEEPINTVL = 10秒; TCP_USER_TIMEOUT = 45秒。
在上述设置条件下,与远端断开物理连接,本段 发送KEEPALIVE探询 直至RESET连接的过程如下:

程序最后一次尝试发送KEEPALIVE探询包(09:25:09)时,距离 最后一次收到ACK(09:24:18),50秒,超过了TCP_USER_TIMEOUT(45秒),发送Reset。
小结2:
命令 ss -not state all 可查看 tcp套接字的状态及keepalive定时器信息。

其中,
① timer:(keepalive, 4.704ms,0) 包含 KEEPALIVE 相关的定时器和计数器。
需要注意的是,这里 4.704ms这个字段, 并不是真正的 倒计时,而是 轮流显示 TCP_KEEPINTVL 和 (TCP_KEEPIDLE - TCP_KEEPINTVL)的倒数过程 。
就是说,该数值 倒数到0时, socket不一定会发送 KEEPALIVE 探询包。
② 0 这个计数,表示 已发送的 KEEPALIVE探询包的个数。 (同时也表示 未被ACK的探寻包的个数)
小结3:

本端发送KEEPALIVE探询包的间隔,不受远端KEEPALIVE探询包的影响。
小结4:
① 超时参数: KEEPIDLE = 30; KEEPINTVAL = 30 ; TCP_USER_TIMEOUT = 65*1000 。 
可见,总共只发送了2个探寻包,就 Reset了。
② 超时参数: KEEPIDLE = 30; KEEPINTVAL = 30 ; TCP_USER_TIMEOUT = 40*1000 。

③ 超时参数: KEEPIDLE = 30; KEEPINTVAL = 30 ; TCP_USER_TIMEOUT = 15*1000 。

可见,②和③设置下,都只发送了1个探寻包,之后 Reset 。
因此,从抓包情况看,TCP_USER_TIMEOUT的实际效果是:
当出现 未应答 情况后,下次发送时,检查 从最后收到 ACK 开始 是否超过设定值,决定 是否需要Reset 。
④ 超时参数: KEEPIDLE = 20; KEEPINTVAL = 10 ; TCP_USER_TIMEOUT = 55*1000 。

这里 发送了4次KEEPALIVE后,又发送了一次。 why?
⑤ 超时参数: KEEPIDLE = 20; KEEPINTVAL = 10 ; TCP_USER_TIMEOUT = 65*1000 。

可见,即使 TCP_KEEPCNT = 3 , 底层还是会再次发送 KEEPALIVE 探询包。
超时参数: KEEPIDLE = 20; KEEPINTVAL = 10 ; TCP_USER_TIMEOUT = 85*1000 。

对应的 ss -not state all | grep 7777 :

小结5:
对于虚拟机环境下,当目标主机down网卡来模拟物理连接断开时,
源主机上,目标主机的arp条目会被清除。
实验时,需要 arp -s 手工设置 mac地址,否则影响抓包观察。
附,操作系统及内核版本:

附: https://xujianhai.fun/posts/tcp_user_timeout/



浙公网安备 33010602011771号