TCP三次握手
序列号:建立连接时计算机随机生成的随机数作为初始值,通过SYN包传给接收端主机,每发送一次数据就累加一次该数据字节数的大小。用来解决网络包乱序问题。
确认应答号:指下一次期望收到的数据的序列号,发送端收到这个确认应答以后认为在这个序号以前的数据都已经被正常接受。用来解决丢包问题。
ACK:确认应答的字段变为有效,TCP规定除了最初建立的SYN包外该位必须设置为1
RST:表示TCP连接中出现异常必须强制断开连接
SYN:表示希望建立连接,并在其序列号的字段进行序列号初始值的设定
FIN:表示今后不会再有数据发送,希望断开连接。通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位为1的TCP段。
使用TCP前必须先建立连接,建立连接是通过三次握手来进行的。
三次握手中,第三次握手是可以携带数据的,前两次握手是不可以携带数据的。一旦完成三次握手,双方都处于ESTABLISHED
状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。
Linux系统查看TCP状态
netstat -napt
TCP四次挥手
客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
客户端收到服务端的 ACK 应答报文后,之后进入 ==FIN_WAIT_2 ==状态。
等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
为什么挥手需要四次?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送(closed_wait),等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。
第一次挥手丢失,会发生什么?
根据 tcp_orphan_retries 决定重传次数
第二次挥手丢失,会发生什么?
由于ACK不会重传,如果服务端第二次挥手丢失客户端就会触发超时重传机制,重传FIN报文
根据 tcp_orphan_retries 决定重传次数
注意:
对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
如果主动关闭方使用 shutdown 函数关闭连接,指定了只关闭发送方向,而接收方向并没有关闭,那么意味着主动关闭方还是可以接收数据的,就会死等对方的FIN。
第三次挥手丢失,会发生什么?
同理第一次挥手丢失
第四次挥手丢失,会发生什么?
在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。
客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL为报文最大生存时间(单位是时间)TTL是经过路由跳数,因此MSL应该大于等于TTL消耗为0的时间确保报文已被自然消亡。
当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
至少运行报文丢失一次
连续两次丢包概率万分之一,概率太小,忽略它比解决它更有性价比
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
为什么需要TIME_WAIT状态?
主动发起关闭连接的一方才会有TIME_WAIT。
两个原因:
- 防止历史连接的数据被后面相同四元组的连接错误的接收
- 保证被关闭连接的一方,能被正确的关闭
原因一:
序列号和初始化序列号并不是无限递增的,会发生回绕为初始值的情况,这意味着无法根据序列号来判断新老数据。
假设TIME_WAIT没有等待时间或时间过短,被延迟的数据包抵达后会发生什么?
相当于重新打开新连接,前面被延迟的SEQ=301抵达客户端,而且这个报文的序列号刚好在客户端接收窗口内,因此客户端会正常接收这个数据报文,但这个数据报文是上一个连接残留下来的,这样就产生数据错乱等严重的问题。
因此为了防止历史连接中的数据被后面相同四元组的连接错误的接收,TCO设计了TIME_WAIT状态,状态会持续2MSL时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中自然消失,再出现的数据包一定都是新建立连接所产生的。
原因二:
等待足够的时间来确保最后的ACK能让被动关闭方接收,从而帮助其正常关闭。
服务端收到这个 RST 并将其解释为一个错误(Connection reset by peer),这对于一个可靠的协议来说不是一个优雅的终止方式。
为防止这种情况,客户端等足够长时间确保服务端能够收到ACK,如果服务端没有收到ACK就会触发TCP重传机制,服务端会重新发生一个FIN,这样一去一来刚好两个MSL时间。
客户端在收到服务端重传的 FIN 报文时,TIME_WAIT 状态的等待时间,会重置回 2MSL。
TIME_WAIT 过多有什么危害?
占用系统资源,占用端口资源。
注意一个四元组唯一确定一个TCP连接。
服务器出现大量的TIME_WAIT状态原因有哪些?
如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接。
服务端主动断开连接的场景:
- HTTP没有使用长连接
- HTTP长连接超时
- HTTP长连接请求数量达到上限
场景一:根据大多数 Web 服务的实现,不管哪一方禁用了 HTTP Keep-Alive,都是由服务端主动关闭连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
当服务端出现大量的 TIME_WAIT 状态连接的时候,可以排查下是否客户端和服务端都开启了 HTTP Keep-Alive,因为任意一方没有开启 HTTP Keep-Alive,都会导致服务端在处理完一个 HTTP 请求后,就主动关闭连接,此时服务端上就会出现大量的 TIME_WAIT 状态的连接。
场景二:为了避免资源浪费的情况,web 服务软件一般都会提供一个参数,用来指定 HTTP 长连接的超时时间,比如 nginx 提供的 keepalive_timeout 参数。假设设置了 HTTP 长连接的超时时间是 60 秒,nginx 就会启动一个「定时器」,如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,nginx 就会触发回调函数来关闭该连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
如果现象是有大量的客户端建立完 TCP 连接后,很长一段时间没有发送数据,那么大概率就是因为 HTTP 长连接超时,导致服务端主动关闭连接,产生大量处于 TIME_WAIT 状态的连接。可以往网络问题的方向排查,比如是否是因为网络问题,导致客户端发送的数据一直没有被服务端接收到,以至于 HTTP 长连接超时。
场景三:
nginx 的 keepalive_requests 这个参数,这个参数是指一个 HTTP 长连接建立之后,nginx 就会为这个连接设置一个计数器,记录这个 HTTP 长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则 nginx 会主动关闭这个长连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
对于一些 QPS 比较高的场景,比如超过 10000 QPS,甚至达到 30000 , 50000 甚至更高,如果 keepalive_requests 参数值是 100,这时候就 nginx 就会很频繁地关闭连接,那么此时服务端上就会出大量的 TIME_WAIT 状态。针对这个场景下,解决的方式也很简单,调大 nginx 的 keepalive_requests 参数就行。
服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。发生这种情况的时候,如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于ESTABLISH
状态,占用着系统资源。
TCP有一个保活机制:
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP连接信息由内核维护,当服务端的进程崩溃后,内核需要回收进程所有TCP连接资源,于是内核会发送第一次挥手FIN报文,后续的挥手过程也都是内核完成,无需进程参与,因此即使服务端进程退出,还是能与客户端完成TCP四次挥手操作。