声明:本文转载自运维社区,微信公共号文章。
前言
在Web性能优化中,我们经常会调整很多TCP相关的性能参数,那么今天我们深入理解一下TCP协议的11种状态变迁。我相信大家已经对于TCP连接的三次握手和四次挥手,并不陌生。在这其中TCP定义了11种状态,下面我们来看看TCP的状态转换。
TCP状态变迁图:
首先,在开始理解这个TCP状态转换图之前,我们需要明确,在实际情况下我们的Web服务器,会同时充当两种角色:
- 充当服务器端的角色:就是作为Web服务器,等待客户端来进行请求。
- 充当客户端的角色:Web服务器作为请求的客户端请求后端的Redis或者MySQL等,或者请求其它互联网上的服务。如果是作为反向代理,那么会请求后端的其它Web服务器。
备注:客户端和服务器端都可以主动发起关闭。
三次握手的状态变迁
要搞明白一个HTTP请求的时候,在TCP层面的11种状态转换,那么就需要我们的思路在客户端和服务器端两种角色之间不停的变换。
客户端:
我们先从实线开始看,实线是客户端的状态变迁、虚线是服务器端的状态变迁。所以先看图的右半侧。
CLOSED:作为起始状态。
SYN_SENT:(CLOSED->SYN_SENT)
当某个应用进程在CLOSED状态下执行主动打开时,那就开始了TCP的三次握手,TCP将发送一个SYN,然后这个客户端的状态由CLOSE转换为SYN_SENT状态。
注意,我们先不要考虑服务器端的状态,我们继续看客户端。
ESTABLISHED:(SYN_SENT-> ESTABLISHED)
那么这个时候根据TCP的三次握手,服务器端会发送一个带ACK的SYN给客户端,那么客户端收到后,会发送一个ACK给服务器端,这个时候客户端的状态就会从SYN_SENT转换为ESTABLISHED,那么这个状态我们应该非常熟悉,因为这个是数据传送发生的一个状态,表示已经建立TCP连接,并且可以进行数据传送。可以看到图中标注的是收:SYN,ACK 发:ACK。
服务器端:
请注意!先不要看ESTABLISHED以下的状态转换,我们现在马上把视角转换到服务器端,看看服务器端在刚才的过程中都经历了哪些状态,我们需要看图的左半侧。
LISTEN:
比如我们客户端请求的是一个Web服务器,例如是Nginx服务,它监听在80端口,那么此时的TCP状态为LISTEN,注意服务器端的起始状态也是CLOSE。
SYN_RCVD:(LISTEN->SYN_RCVD)
好的,现在处于LISTEN状态的Nginx服务器收到了客户端发过来的SYN,然后自身状态从LISTEN转换为SYN_RCVD,我们想想此时客户端处于什么状态,与之相对应的SYN_SENT。服务器端是收SYN,发送SYN和ACK给客户端。
ESTABLISHED:(SYN_RCVD> ESTABLISHED)
服务器端发送完毕SYN和ACK后,客户端会返回一个ACK,那么当服务器端接收到这个ACK后,状态从SYN_RCVD转换到了ESTABLISHED。
好的,我们刚才已经详细说明了,在TCP建立连接过程中,客户端和服务器端的状态转换,先不要继续往下看这个大图,我们在通过下面的小图按照TCP三次握手的方式,再次来梳理一下流程。
1、客户端执行主动打开,发送seq=x。SYN标志位置为1。状态从CLOSED到SYN_SENT。
2、服务器端被动打开,从CLOSED到LISTEND,收到客户端的SYN后,TCP标志位SYN、AC都置为1,然后回复确认号ack=x+1,同时也发送一个序列号seq=y。状态从LISTEN转换为SYN_RCVD。
3、客户端收到服务器端的确认后,将TCP标志位ACK置为1,确认号是ack=y+1,发送序号seq=x+1,此时进入ESTABLISHEN状态
4、服务器收到客户端发的信息后,也进入EXTABLISHED状态。
TCP四次挥手的连接状态:
我们需要继续看这个状态转换的大图,这次我们要从ESTABLISHED开始往下看。这次我们还是从客户端开始看。
客户端FIN_WAIT_1:(ESTABLISHED->FIN_WAIT_1):客户端在ESTABLISHED的状态下,发送FIN给服务器端要求关闭连接,这个时候,客户端的状态从ESTABLISHED转换为FIN_WAIT_1。
服务器端CLOSE_WAIT:(ESTABLISHED->CLOSE_WAIT)好的,我们先将客户端的状态暂停下,回过来头来看服务器端,那么服务器端收到客户端发过来的FIN后,立即发送一个ACK确认,然后状态从ESTABLISHED转换到了CLOSE_WAIT。
客户端FIN_WAIT_2:(FIN_WAIT_1->FIN_WAIT_2)好的,我们再回来到客户端,刚才服务器端给他发了一个ACK,也就是它收到了客户端的FIN的报文,这个时候,客户端马上从FIN_WAIT_1转换到FIN_WAIT_2。
服务端LAST_ACK:(CLOSE_WAIT->LAST_ACK)
好的注意此时客户端不能再给服务端发送任何的数据了,但是服务端能给客户端发。此时就是我们说的“半关闭”状态。那么此时服务端在干啥呢,他通知上层应用,比如HTTP,关闭连接,当上层的数据发送完毕后,通知TCP说可以释放连接了,此时服务端给客户端发送一个FIN。然后自己从CLOSE_WAIT状态转换为了LAST_ACK状态。那么如果上层应用吃吃不关闭呢?大家考虑下回发生什么。
客户端TIME_WAIT:(FIN_WAIT_2->TIME_WAIT)刚才服务器端给客户端发送了一个FIN后进入到了LAST_ACK的状态,那么客户端收到这个FIN后,从FIN_WAIT_2转换为TIME_WAIT。注意同时客户端会发回一个ACK给服务器端。
服务器端CLOSED:(LAST_ACK->CLOSED)我们先不管客户端,现在服务器端收到刚才客户端发送的ACK后,状态从LAST_ACK转换为了CLOSED状态。好的,服务端这边完事了。
客户端:客户端还在TIME_WAIT呢。我们去看看它。
TIME_WAIT状态有两个存在的理由:
1、可靠地实现TCP全双工连接的终止;
2、允许老的重复分节在网络中消逝。
那么在Linux下,这个状态要持续60秒,然后客户端的状态变换为CLOSED。
转载请注明:西门飞冰的博客 » TCP协议的11种状态变迁