关于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/

 

 

posted @ 2025-06-30 22:34  rivanwang  阅读(155)  评论(0)    收藏  举报