TCP — 状态机

发布于 2021-09-18  46 次阅读


状态机总览

客户端与服务端通过TCP建立连接的状态转移如图所示:

图中一共包括TCP的11个状态:

  • 1、 LISTEN:TCP正等待从对端TCP节点发来的连接请求
  • 2、 SYN_SENT:TCP发送一个SYN报文,代表应用程序执行了一个主动打开的操作,并等待对端回应以此完成连接的建立
  • 3、 SYN_RECV:之前处在LISTEN状态的TCP节点收到了对端发送的SYN报文,并已经通过发送SYN/ACK报文做出了相应(即,这个TCP报文同时设置了SYN和ACK位),正等到对端TCP节点发送一个ACK以此完成连接的建立,进入ESTABLISHED
  • 4、 ESTABLISHED:与对端TCP节点间的连接建立完成。数据报文此时可以再两个TCP节点间双向交换
  • 5、 FIN_WAIT1:应用程序关闭了连接。TCP节点发送一个FIN报文到对端,以此终止本段的连接,并等待对端发来的ACK。这个状态以及接下来的三种状态都与应用程序执行主动关闭有关——也就是说,首先关闭本段连接的应用程序
  • 6、 FIN_WAIT2:之前处在FIN_WAIT1状态的TCP节点现在已经收到了对端TCP节点发来的ACK(半关闭状态)
  • 7、 CLOSING:之前处在FIN_WAIT1状态的TCP节点正在等待对端发送ACK,但却收到了FIN。这表示对端也正在尝试执行一个主动关闭。(换句话说,这两个TCP节点几乎在同一时刻发送了FIN报文。即同时关闭)
  • 8、 TIME_WAIT:完成主动关闭后,TCP节点接收到了FIN报文。这表示对端执行了一个被动关闭。此时这个TCP节点将在TIME_WAIT状态中等待一段固定的时间,这是为了确保TCP连接能够可靠的终止,同时为了确保任何老的重复报文在重新建立同样的连接之前在网络中超时小时。当这个固定的时间段超时后,连接就关闭了,相关的内核资源得到释放。
  • 9、 CLOSE_WAIT:TCP节点从对端收到FIN报文后将处在CLOSE_WAIT状态。该状态以及接下来的一个状态都同应用程序执行的被动关闭有关,也就是第二个执行关闭操作的应用。
  • 10、LAST_ACK:应用程序执行被动关闭,而之前处于CLOSE_WAIT状态的TCO节点发送一个FIN报文给对端,并等待对端的确认。当收到对端发来的确认ACK报文时,连接关闭,相关的内核资源都会得到释放。
  • 11、LCLOSED:关闭状态。

此外还有一个RST报文,当出现上图以外的状态转移时,会发送RST报文,即连接异常终止。直接释放连接。

连接建立过程

三次握手过程分析

(1)首先A向B发出连接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序号 seq=x。TCP规定,SYN报文段不能携带数据,但要消耗掉一个序号。这时,A进入SYN-SENT状态。【备注:序号指的是 TCP 报文段首部20字节里的序号,TCP 连接传送的字节流的每一个字节都按顺序编号,具体可以看看 TCP 可靠传输实现的原理】
(2)B收到请求后,向A发送确认。在确认报文段中把SYN和ACK位都置为1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时B进入SYN-RCVD状态。
(3)A收到B的确认后,还要向B给出确认。确认报文段的ACK置为1,确认号ack=y+1,而自己的序号seq=x+1。这时,TCP连接已经建立,A进入ESTABLISHED 状态,当B收到A的确认后,也会进入 ESTABLISHED 状态。

常见问题

2.1 为什么需要三次握手过程(面试经常问)

本质上是为了避免为无效连接分配资源,为了确认双方均处于可收发状态,最少需要三次握手。即:

  • 客户端发起连接请求
  • 服务端确认连接请求,并询问客户端是否可以建立连接
  • 客户端确认连接请求 --> 连接建立。

如果是两次:

A --SYN--> B

A <--ACK-- B ( ESTABLISHED )

A ( ESTABLISHED )

可以看到服务端在一次SYN报文之后就进入了 ESTABLISHED 状态。假设A建立连接,第一次失败了,SYN报文在网络中滞留。A进行了重传,成功建立连接之后传输了数据,最后关闭了连接。

此时先前的SYN报文在滞留之后到达了B,B直接进入了 ESTABLISHED 状态 ,发送ACK,但此时A并没有想建立连接,不会响应报文。而B认为连接已经建立,这种情况下就会出现连接资源的浪费。

如果是四次:

A --SYN--> B

A <--ACK-- B

A <--SYN-- B

A --ACK--> B

A ( ESTABLISHED ) B ( ESTABLISHED )

这种情况下就不会出现无效的SYN报文引起的无效连接。但可以看到,B需要先发送一个ACK表示收到SYN,之后再发送自己的SYN,这两步之间不需要客户端进行任何状态转移,所以将这两步合并,发送ACK报文的同时带上自己的SYN 标志,最后就成了三次握手。

2.2 如果在TCP第三次握手中的报文段丢失了会发生什么情况?

Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。

双方同时建立连接的特殊情况

实际上就是双方都进行了三次握手。

连接关闭过程

四次挥手过程分析

四次挥手实际上是两次两次挥手:

前两次:

(1)客户端 A 的 TCP 进程先向服务端发出连接释放报文段,并停止发送数据,主动关闭 TCP 连接。释放连接报文段中 FIN=1,序号为 seq=u,该序号等于前面已经传送过去的数据的最后一个字节的序号加1。这时,A进入 FIN—WAIT-1 (终止等待1)状态,等待 B 的确认。TCP 规定,FIN报文段即使不携带数据,也要消耗掉一个序号。这是 TCP 连接释放的第一次挥手。
(2)B收到连接释放报文段后即发出确认释放连接的报文段,该报文段中,ACK=1,确认号为ack=u+1,其自己的序号为v,该序号等于B前面已经传送过的数据的最后一个字节的序号加1。然后B进入CLOSE—WAIT(关闭等待)状态,此时TCP服务器进程应该通知上层的应用进程,因而A到B这个方向的连接就释放了,这时TCP处于半关闭状态,即A已经没有数据要发了,但B若发送数据,A仍要接受,也就是说从B到A这个方向的连接并没有关闭,这个状态可能会持续一些时间。这是TCP连接释放的第二次挥手。

后两次:

3)A收到B的确认后,就进入了FIN—WAIT(终止等待2)状态,等待B发出连接释放报文段,如果B已经没有要向A发送的数据了,其应用进程就通知TCP释放连接。这时B发出的链接释放报文段中,FIN=1,确认号还必须重复上次已发送过的确认号,即ack=u+1,序号seq=w,因为在半关闭状态B可能又发送了一些数据,因此该序号为半关闭状态发送的数据的最后一个字节的序号加1。这时B进入LAST—ACK(最后确认)状态,等待A的确认,这是TCP连接的第三次挥手。

(4)A收到B的连接释放请求后,必须对此发出确认。确认报文段中,ACK=1,确认号ack=w+1,而自己的序号seq=u+1,而后进入TIME—WAIT(时间等待)状态。这时候,TCP连接还没有释放掉,必须经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态,时间MSL叫做最长报文寿命,RFC建议设为2分钟,因此从A进入TIME—WAIT状态后,要经过4分钟才能进入到CLOSED状态,而B只要收到了A的确认后,就进入了CLOSED状态。二者都进入CLOSED状态后,连接就完全释放了,这是TCP连接的第四次挥手。

常见问题

3.1 为什么要有四次挥手过程

首先是客户端发送终止连接请求 --> 一次

之后服务端响应终止连接请求(此时服务端可能还存在未发送的报文,所以服务端不能直接发送终止连接请求) --> 二次

服务端数据发送完毕,发送终止连接请求 --> 三次

的客户端确认关闭 --> 四次

这里的重点在于最后一次为什么还需要客户端的响应。因为之前 服务端响应终止连接请求 的时候,客户端进入了半关闭状态,等待服务端把数据传输完。但如果服务端发送的终止连接请求没有到达呢?这样客户端就没有办法正常关闭了。所以需要确认。

3.2 为什么有一个时长为2MSL的TIME_WAIT阶段

这里是用来处理最后一个ACK报文丢失的情况。服务端没有接收到这个ACK报文的时候,会认为自己的FIN报文丢失,并重传FIN。客户端此时就可以继续重传一个ACK。

当服务端重传FIN超过上限之后,服务端也直接关闭了,此时网络中可能存在大量的FIN和ACK包。如果直接建立连接,客户端可能会收到过去的FIN,服务端可能会收到异常的ACK,之后就都会发送RST重置连接。

至于为什么是2MSL,考虑客户端发送ACK到确认超时是1MSL,服务端重传的FIN到客户端最大也要经过1MSL,所以在客户端发送ACK之后的2MSL时间内,没有收到服务端重传的FIN,则认为对方已经收到ACK了,于是连接可以正常关闭。

双方同时关闭连接的特殊情况

由于双方都对FIN进行了响应,所以直接进入TIME_WAIT


当其他人都认为你要鸽的时候,你鸽了,亦是一种不鸽