共计 2257 个字符,预计需要花费 6 分钟才能阅读完成。
有个朋友去字节面试,说被问到这么一个问题:服务端挂了,客户端TCP连接会发生什么?
他的回答是会与客户端进行四次挥手,断开连接。
这个回答不能说错,也不能说对,只是没有回答到点上。
如果【服务端挂掉】是指 服务端进程崩溃,那么这个回答的结论是对的,但是没有说明为什么。
如果【服务端挂掉】是指 服务端主机宕机呢?
所以说,遇到这种半开放性的问题,不能自己直接假设前提,而是要把前提展开来分析。
一、服务端进程奔溃,客户端会发生什么?
TCP的连接信息都是由内核维护的,所以当服务端的进程奔溃后,内核需要回收该进程的所有TCP连接资源。
此时内核会发送第一次挥手的FIN报文,后续的挥手过程都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成TCP的四次挥手的过程。
可以简单模拟测试下,使用kill -9 命令来杀掉进程后,发现在kill进程后,服务端还是会发送 FIN 报文,与客户端吧进行四次挥手。
二、服务端主机宕机后,客户端会发生什么?
当服务端的主机断电或者重启了,这种情况就属于服务端宕机了。
当服务端发生宕机后,是没办法和客户端进行四次挥手的,所以在服务端主机宕机的那一时刻,客户端也是没有办法感知到服务端主机宕机了,只能在后续的数据交互中来感知服务端的连接不存在了。
这个时候,我们需要分两种情况来分析:
- 服务端宕机后,客户端会发送数据
- 服务端宕机后,客户端一直不会发送数据
2-1 服务端主机宕机后,如果客户端发送数据会怎么样?
客户端发送了数据报文,由于得不到响应,在等待一定时间后,客户端就会触发 超时重传 机制,重传未得到响应的数据报文。
当重传次数达到一定阈值后,内核就会判定TCP连接有问题,然后通过Socket接口告诉应用程序该TCP连接出问题了,于是客户端的TCP连接就会断开。
问题:重传几次才能断开呢?
在Linux中,提供了一个tcp_retries2的配置项,默认15
这个内核参数就是控制TCP连接建立的情况下,超时重传的次数。
不过也不代表必须重试15次才会终止连接,还跟超时时间有关。
在发生超时重传的过程中,每一轮的超时时间(RTO)是倍数增长的,默认第一轮是200ms,第二轮400ms,第三轮800ms,以此类推,所以重试15次的总timeout是924600ms。
而RTO是基于 RTT(一个包的往返时间)来计算的,如果 RTT 较大,那么计算出来的 RTO 就越大,那么经过几轮的重传后,很快会到达 timeout 值。
- 如果 RTT 比较小,那么 RTO 初始值就约等于下限 200ms,也就是第一轮的超时时间是 200 毫秒,由于 timeout 总时长是924600 ms,表现出来的现象刚好就是重传了 15 次,超过了 timeout 值,从而断开 TCP 连接
- 如果 RTT 比较大,假设 RTO 初始值计算得到的是 1000 ms,也就是第一轮的超时时间是 1 秒,那么根本不需要重传 15 次,重传总间隔就会超过 924600 ms
2-2 服务端主机宕机后,如果客户端一直不发数据
服务端主机宕机后,如果客户端一直不发送数据,那么还得看是否开启了 TCP keepalive 机制(保活机制)。
如果 没有开启 TCP keepalive 机制,那么客户端的 TCP 连接将一直保持存在。
所以在没有TCP keepalive 机制,且双方不传输数据的情况下,一方的TCP连接处于 established 状态时,也并不能代表另一方的 TCP 连接还一定是正常的。
如果 开启 了 TCP keepalive 机制,在服务端主机宕机后,客户端一直不发送数据,在持续一段时间后,TCP 就会发送探测报文,探测服务端是否存活:
- 如果对端是正常工作的。当 TCP 保活的探测报文发送到对端,对端会正常响应,这样 TCP 保活的时间就会重置,等待下一个 TCP 保活时间的到来。
- 如果对端主机宕机,或者其他原因比如网络波动导致探测报文不可达。客户单得不到响应,连续几次,达到保活探测次数后,内核会报告该 TCP 连接已死亡。
所以 TCP keepalive 机制可以在双方没有数据交互的情况下,通过探测报文确定对方的TCP连接
TCP keepalive机制原理:
定义一个探测间隔,在这个间隔期内,如果没有任何连接相关的活动,TCP keepalive机制就会开始工作,每隔一个间隔,发送一个探测报文,该报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的TCP连接已经死亡,系统内核会将错误信息上报给上层应用程序。
Linux默认的相关参数如下
net.ipv4.tcp_keepalive_time=7200 //保活时间7200s(2小时) 就是说2小时内没有任何连接活动 就会启动保活机制
net.ipv4.tcp_keepalive_intvl=75 //每次检测间隔75s
net.ipv4.tcp_keepalive_probes=9 //检测的最大次数 超过这个次数 就认为对方不可达
这是Linux的默认的参数,也就是兜底的配置,所以探测的时间比较长,最少要经过 7200(759)=7975s(2小时11分15秒)才可以发现一个死亡连接。
实际上,应用层是可以自己实现一套探测机制的,通过 socket 接口设置 SO_KEEPALIVE 选项生效,如果没有设置,那么就无法使用TCP保活机制。
一般常见的net连接的框架,都会提供 keepalive_timeout 参数,用来指定 HTTP 长连接的超时时间。如果设置了60s,那么框架就会启动一个定时器,客户端再完成一个HTTP请求后,在60s内都没有再发起新的请求,就会触发回调函数来释放连接。