关闭连接时,被动断开⽅在收到对⽅的FIN结束请求报⽂时,很可能业务数据没有发送完成,并不能⽴即关闭连接,被动⽅只能先回复⼀个ACK响应报⽂,告诉主动断开⽅: “你发的FIN报⽂我收到了,只有等到我所有的业务报⽂都发送完了,我才能真正的结 束,在结束之前,我会发你FIN+ACK报⽂的,你先等着”。所以,被动断开⽅的确认报⽂,需要拆开成为两步,故总体就需要四步挥⼿。
⽽在建⽴连接场景中,Server端的应答可以稍微简单⼀些。当Server端收到Client端的SYN连接请求报⽂后,其中ACK报⽂表示对请求报⽂的应答,SYN报⽂⽤来表示服务端的连接也已经同步开启了,⽽ACK报⽂和SYN报⽂之间,不会有其他报⽂需要发送,故⽽可以合⼆为⼀,可以直接发送⼀个SYN+ACK报⽂。所以,在建⽴连接时,只需要三次握⼿即可。
三次握⼿完成两个重要的功能:⼀是双⽅都做好发送数据的准备⼯作,⽽且双⽅都知道对⽅已准备好;⼆是双⽅完成初始SN序列号的协商,双⽅的SN序列号在握⼿过程中被发送和确认。
如果把三次握⼿改成两次握⼿,可能发⽣死锁。两次握⼿的话,缺失了Client的⼆次确认ACK帧,假想的TCP建⽴的连接时⼆次挥⼿,可以如下图所示:
在假想的TCP建⽴的连接时⼆次握⼿过程中,Client发送Server发送⼀个SYN请求帧,Server收到后发送了确认应答SYN+ACK帧。按照两次握⼿的协定,Server认为连接已经成功地建⽴了,可以开始发送数据帧。这个过程中,如果确认应答SYN+ACK帧在传输中被丢失,Client没有收到,Client将不知道Server是否已准备好,也不知道Server的SN序列号,Client认为连接还未建⽴成功,将忽略Server发来的任何数据分组,会⼀直等待Server的SYN+ACK确认应答帧。⽽Server在发出的数据帧后,⼀直没有收到对应的ACK确认后就会产⽣超时,重复发送同样的数据帧。这样就形成了死锁。
原因之⼀:主动断开⽅等待2MSL的时间,是为了确保两端都能最终关闭。假设⽹络是不可靠的,被动断开⽅发送FIN+ACK报⽂后,其主动⽅的ACK响应报⽂有可能丢失,这时候的被动断开⽅处于LAST-ACK状态的,由于收不到ACK确认被动⽅⼀直不能正常的进⼊CLOSED状态。在这种场景下,被动断开⽅会超时重传FIN+ACK断开响应报⽂,如果主动断开⽅在2MSL时间内,收
到这个重传的FIN+ACK报⽂,会重传⼀次ACK报⽂,后再⼀次重新启动2MSL计时等待,这样,就能确保被动断开⽅能收到ACK报⽂,从⽽能确保被动⽅顺利进⼊到CLOSED状态。只有这样,双⽅都能够确保关闭。反过来说,如果主动断开⽅在发送完ACK响应报⽂后,不是进⼊TIME_WAIT状态去等待2MSL时间,⽽是⽴即释放连接,则将⽆法收到被动⽅重传的FIN+ACK报⽂,所以不会再发送⼀次ACK确认报⽂,此时处于LAST-ACK状态的被动断开⽅,⽆法正常进⼊到CLOSED状态。
原因之⼆:防⽌“旧连接的已失效的数据报⽂”出现在新连接中。主动断开⽅在发送完最后⼀个ACK报⽂后,再经过2MSL,才能最终关闭和释放端⼝,这就意味着,相同端⼝的新TCP新连接,需要在2MSL的时间之后,才能够正常的建⽴。2MSL这段时间内,旧连接所产⽣的所有数据报⽂,都已经从⽹络中消失了,从⽽,确保了下⼀个新的连接中不会出现这种旧连接请求报⽂。
TCP还设有⼀个保活计时器,Client端如果出现故障,Server端不能⼀直等下去,这样会浪费系统资源。每收到⼀次Client客户端的数据帧后,Server端都的保活计时器会复位。计时器的超时时间通常是设置为2⼩时,若2⼩时还没有收到Client端的任何数据帧,Server端就会发送⼀个探测报⽂段,以后每隔75秒钟发送⼀次。若⼀连发送10个探测报⽂仍然没反应,Server端就认为
Client端出了故障,接着就关闭连接。如果觉得保活计时器的两个多⼩时的间隔太⻓,可以⾃⾏调整TCP连接的保活参数。