TCP/IP协议族的传输层基础(4)——TCP协议的状态转移

TCP的状态转化

上一篇文章我们已经看到了TCP连接的两方会处于不同的状态,我们再来看一下这张图。

 

下图是TCP完整的状态转化图,红色线是一般的服务器状态转移的线,蓝色线是一般的客户端的状态转移的线,灰色线表示特殊情况下的状态转移。

 

 

 

下面我们来看一下这些状态转化的具体过程

服务器端的状态转化

  • ①[CLOSED-->LISTEN]:服务器调用listen后进入LISTEN状态,程序被动打开,等待客户端主动连接

  • ②[LISTEN-->SYN_RCVD]:服务器收到带有SYN的同步报文段的连接请求,就将该连接放入内核等待队列中,并向客户端发送SYN确认报文(SYN+ACK)

  • ③[SYB_RCVD-->ESTABLISHED]:服务器一旦受到客户端的确认报文(三次握手的最后一次握手),就进入ESTABLISHED状态,就可以进行读写操作了

  • ④[ESTABLISHED-->CLOSE_WAIT]:当客户端主动关闭连接,服务器会接收到带有FIN的结束报文段,服务器返回一个确认报文段(ACK)并进入CLOSE_WAIT等待关闭连接(需要处理完之前的数据,比如说还有一些数据没有发完,需要继续发送完)

  • ⑤[CLOSE_WAIT-->LAST_ACK]:当服务器真正调用close关闭连接时,会向客户端发送FIN结束报文段,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认到了FIN,并发送四次挥手的最后一次挥手)

  • ⑥[LAST_ACK-->CLOSED]:服务器接收到了客户端的ACK确认报文,彻底关闭连接

 

客户端的状态转化

  • ①[CLOSED-->SYN_SENT]:客户端调用connect发起连接请求,程序主动打开,向服务器发送SYN同步报文段,进入SYN_SENT状态等待服务器的回应

  • ②[SYN_SENT-->ESTABLISHED]:客户端接收到服务器传来的同步确认报文段(SYN+ACK),connect调用成功,并向服务器发送一个ACK确认报文段表示收到了服务器的确认报文(三次握手的最后一次握手),并进入ESTABLISHED状态,开始读写数据

  • ③[ESTABLISHED-->FIN_WAIT_1]:客户端主动调用close尝试主动关闭连接,向服务器发送FIN结束报文段,同时进入FIN_WAIT_1

  • ④[FIN_WAIT_1-->FIN_WAIT_2]:客户端接收到服务器对结束报文段的确认报文,进入FIN_WAIT_2,开始等待服务器的FIN结束报文

  • ⑤[FIN_WAIT_2-->TIME_WAIT]:客户端接收到服务器发来的FIN结束报文,进入TIME_WAIT,并发出最后一个LAST_ACK确认报文。关于TIME_WAIT状态,下面再来探讨

  • ⑥[TIME_WAIT-->CLOSED]:客户端要等待一个最多2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态

 

特殊情况的状态转化

  • [SYN_RCVD-->LISTEN]:服务器进入到SYN_RCVD状态以后,向客户端发送同步确认报文,等待客户端发送最后一个ACK确认报文。此时如果客户端调用了close关闭连接(下面的④),那么服务器此时就是半打开状态(即服务器单独维持这个连接,客户端已经退出连接),此时服务器再尝试往这个连接中发送数据的时候(这里就是发送ACK确认报文段),客户端就会回应一个RST复位报文段,告知服务器尝试重新建立连接或是关闭连接。服务器如果收到了这个复位报文段,就变为LISTEN状态变为CLOSED状态。

  • [LISTEN-->SYN_SENT]:这属于服务器主动给客户端发送数据。举个例子,QQ发消息,一个客户端向另一个客户端发送消息,实际上先访问了服务器,服务器收到这个消息之后,再把这个消息发送给另一个客户端,这个时候就是服务器主动向客户端发起连接,服务器发送一个SYN同步报文段,并进入SYN_SENT状态

  • [SYN_SENT-->SYN_RCVD]:这是一个很特殊的情况(同时请求连接)。客户端向服务器请求连接的同时,服务器也向客户端请求连接,这就属于两者同时请求连接。即有一方在发送了SYN同步报文段后,在等待对方发送的SYN+ACK的同步确认报文段的时候,收到了对方发送的SYN同步报文段尝试和自己建立连接,这个时候就会向对端再发送一个SYN+ACK的同步确认报文段,再由SYN_SENT状态转化为SYN_RCVD状态。

  • [SYN_SENT-->CLOSED]:如果在SYN_SENT状态的时候,这个状态的程序就调用close关闭程序;或者是连接超时(根本就没网),没有收到服务器发来的确认应答;又或者是因为访问了服务器不存在的端口号或是处于TIME_WAIT的端口号,收到的是来自服务器的RST复位报文段,服务器告知客户端尝试重新建立连接或是关闭连接,那么客户端就会直接进入CLOSED状态

  • [SYN_RCVD-->FIN_WAIT1]:如果在SYN_RCVD状态的时候,这个状态的程序就调用close关闭程序,那么并不是像上面那样直接进入CLOSED状态,而是向对端发送一个FIN结束报文段,进入FIN_WAIT1状态。这样的状态转移的意思是,接收方在发送了SYN+ACK同步确认报文段之后,在等待对端的ACK确认报文段的时候,突然反悔了不想建立连接了,那么这个时候就调用close主动关闭连接,并发送FIN结束报文段

  • [FIN_WAIT1-->CLOSING]:CLOSING状态是一个及其特殊的状态(同时关闭连接,可以类比上面的③)。服务器和客户端同时向对端发送FIN结束报文段,那么在它们看来,它们自己都是先发送的一方,所以先进入到了FIN_WAIT1状态,但这个时候它们想等的是对端发来的ACK确认报文段,却收到了对端发来的FIN结束报文段,那么这个时候它们就都将自己的状态变为CLOSING状态。

  • [FIN_WAIT1-->TIME_WAIT]:这个状态变化跳过了FIN_WAIT2状态,是因为TCP的捎带应答机制,这个状态的一端同时接收到了对端的FIN报文段和ACK确认报文段,那么就直接进入TIME_WAIT状态。关于捎带应答的原因,可以看上一篇博客的抓包结果(即四次挥手变成了三次挥手)。

  • [CLOSING-->TIME_WAIT]:这个状态变化承接了⑥的状态变化,CLOSING状态的双方,在收到了对方的ACK确认报文段以后,等价于都收到了对方的FIN结束报文段和ACK确认报文段,那么不会再进入FIN_WAIT2状态而是直接跳到了TIME_WAIT状态,这有点像上面⑦的捎带应答

 

ClOSING状态:服务器和客户端同时向对端发送FIN结束报文段,那么在它们看来,它们自己都是先发送的一方,所以先进入到了FIN_WAIT1状态,但这个时候它们想等的是对端发来的ACK确认报文段,却收到了对端发来的FIN结束报文段,此时它们的TCP状态就变为CLOSING,当接收到对方的ACK段后,彼此状态变为:TIME_WAIT,该状态是发起close的一方才会有的状态。

 

 

关于TIME_WAIT状态

  • TCP协议规定,主动关闭的一方要处于TIME_WAIT状态,要等待最多2个MSL的时间后才能回到CLOSED状态。这是属于主动关闭的一方的状态

  • MSL在RFC1122中规定为两分钟,但是每个操作系统的实现不同,本机就是60S

  • 为什么是等待时间是2MSL:因为是2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程迟到的数据,但是这种数据很可能数错误的)

 

TIME_WAIT这个状态的目的最主要的原因有两个:

  • 为了防止ACK丢包。在理论上保证最后一个报文可靠到达,假设最后一个ACK丢失,那么服务器会重新发一个FIN,因此客户端需要停留在某个状态来处理重复收到的服务器发来的FIN报文段。这是客户端仍然可重发一个ACK确认报文。否则如果没有这个TIME_WAIT状态,那么客户端会发送一个RST复位报文段给服务器,但服务器则认为这是一个错误,因为它希望得到的是一个确认报文段

  • 保证让迟来的TCP报文段有足够的时间被识别并丢弃。​一个TCP端口不能被同时打开多次。当一个TCP连接处于TIME_WAIT状态的时候,我们将无法立即使用该连接占着的端口来建立一个连接。如果没有TIME_WAIT状态的话,应用程序能够建立一个和刚关闭的连接相似的连接(指具有相同的IP地址和端口号)。这个新的连接可能接收到属于原来连接的迟到的数据,但是这种连接可能是错误的。所以坚持2MSL能够确保两个传输方向上的迟到的数据都已经消失

 

关于TIME_WAIT状态引起的bind失败的方法

在server的TCP连接没有完全断开之前不允许重新监听,某些情况下是不合理的

  • 服务器需要处理大量的客户端连接(每个连接的生存时间可能很短,但是每秒有大量的客户端来连接)

  • 这个时候如果服务器主动关闭连接(比如某些客户端不活跃,就要被服务器清理掉),就会产生大量的TIME_WAIT连接

  • 由于请求量很大,就可能导致TIME_WAIT的连接数很多,TIME_WAIT的端口还不能立即使用,导致服务器的端口不够用,无法处理新的连接

 

解法方法:使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符

 

在socket()和bind()之间创建如下代码

int opt = 1;

setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

 

 

复位报文段的三种情况

某些情况下,TCP连接的一端会向另一端发送RST复位报文段,来通知对方关闭连接或重新建立连接

  • 访问不存在的端口号或是处于TIME_WAIT状态的端口号:由于该端口号不可用,所以服务器发送一个复位报文段以通知客户端尝试重新建立连接或是放弃连接(对应于上面特殊状态转移中的④)

  • 异常终止连接:TCP提供了异常终止一个连接的方法,即给对方发送一个复位报文段。一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃(可以使用socket选项SO_LINGER来发送复位报文段,用于异常终止连接)​

  • 处理半打开连接:服务器(或客户端)关闭或异常终止了连接,但对方没有接收到结束报文段(比如发送网络故障),此时客户端(或服务器)还维持着原来的连接,而服务器(或客户端)即使重启,也没有该连接的任何消息了。这就是半打开连接,维持连接的一方的状态就是半打开状态。这个时候如果客户端(或服务器)往这个连接中写数据,对方将回应一个复位报文段​(对应于上面特殊状态转移中的①)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值