网络编程----TCP连接的断开(四次挥手)

上个博客讲解了TCP连接建立的全过程,以及其连接双方的各个状态,那么连接断开的时候又是什么情况呢?

TCP断开连接

发起链接的主动方基本都是客户端,但是断开连接的主动方服务器和客户端都可以充当。

四次挥手是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:

由于TCP连接时全双工的,因此,每个方向的数据传输通道都必须要单独进行关闭。以上图为例,主动断开方完成数据发送任务后,发送一个FIN来终止从主动断开方发给被动断开方这一方向的连接,被动断开方收到一个FIN只是意味着从主动断开方发给自己的这一方向上没有数据流动了,即自己不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,可以由被动断开方发给主动断开方,直到这一方向也发送了FIN,连接才真正的完全关闭。

四次挥手过程详解(客户端担任主动断开方)

第一次挥手:

Client发送一个FIN(FIN位为1),其序号seq=u,它等于前面已经传送的数据的最后一个字节的序号+1。用来关闭Client到Server的数据传送,然后Client等待Server的确认,Client进入FIN_WAIT_1状态。(ps:FIN报文段即便不携带数据还是占一个序号)

第二次挥手:

Server收到FIN后,发送一个ACK给Client,ack确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),这个确认报文段自己的序号是seq=v,它是Server前面传送过的数据的最后一个字节序号+1,然后Server进入CLOSE_WAIT状态。TCP的Server进程通知高层应用进程,从Client到Server这个方向的连接关闭,这时TCP连接处于半关闭状态,即Client没有数据发送,但是Server若要发送数据,Client仍要接收,即Server到Client这个方向的连接并未关闭。

第三次挥手:

Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

扫描二维码关注公众号,回复: 5990249 查看本文章

第四次挥手:

Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

  • FIN_WAIT_1 这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:- FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到
  • FIN_WAIT_2 上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。
  • TIME_WAIT 表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态(三次挥手)。
  • CLOSE_WAIT 表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
  • LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

客户端和服务器同时关闭

上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:

CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有先收到对方的ACK报文,反而却先收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是上图当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。

总结:同时断开时,如果主机在FIN_WAIT1状态下首先收到对端主机的FIN包的话,那么该主机在确认已经收到了对端主机全部的Data数据包后,就响应一个ACK给对端主机,然后自己进入CLOSEING状态,主机在CLOSEING状态下收到自己的FIN包的ACK包的话,那么就进入TIME WAIT 状态。于是TCP的主机两端同时发起FIN包进行断开连接,那么两端主机可能出现完全一样的状态转移 FIN_WAIT1——>CLOSEING——->TIME_WAIT,也就会Client和Server最后同时进入TIME_WAIT状态

TCP的TIME_WAIT状态

  • 为什么需要TIME_WAIT状态?

如果主动关闭方不进入TIME_WAIT,那么主动关闭方在发送完ACK就走了的话:如果最后发送的ACK在路由过程中丢掉了,最后没能到被动关闭方,这个时候被动关闭方 没收到自己FIN的ACK就不能关闭连接,接着被动关闭方 会超时重发FIN包,但是这个时候已经没有对端会给该FIN回ACK,被动关闭方就无法正常关闭连接了(虽然最后也会多次发送断开链接,但是浪费时间呀),所以主动关闭方需要进入TIME_WAIT 以便能够重发丢掉的被动关闭方FIN的ACK。另一方面也是为了防止将本次链接的数据出现在下一次链接中。

  • TIME_WAIT状态为什么需要经过2MSL的时间才关闭连接呢

(1)为了保证A发送的最后一个确认报文段能够到达B。这个确认报文段可能会丢失,如果B收不到这个确认报文段,其会重传第三次“挥手”发送的FIN+ACK报文,而A则会在2MSL时间内收到这个重传的报文段,每次A收到这个重传报文段后,就会重启2MSL计时器。这样可以保证A和B都能正常关闭连接。

(2)为了防止被动方发送的已失效的报文段出现在下一次连接中。A经过2MSL时间后,可以保证在本次连接中传输的报文段都在网络中消失,这样一来就能保证在后面的连接中不会出现旧的连接产生的报文段了。

  • TIME_WAIT会带来哪些问题呢

TIME_WAIT带来的问题注意是源于:一个连接进入TIME_WAIT状态后需要等待2*MSL(一般是1到4分钟)那么长的时间才能断开连接释放连接占用的资源,会造成以下问题

(1)作为服务器,短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接,占据大量的tuple,严重消耗着服务器的资源。

(2)作为客户端,短时间内大量的短连接,会大量消耗的Client机器的端口,毕竟端口只有65535个,端口被耗尽了,后续就无法在发起新的连接了。

  • 如何清理TIME_WAIT状态?

使用RST包来终止掉处于TIME_WAIT状态的链接,详情可以看RST攻击

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/84751662