通信过程

TCP 连接通信过程

建立 TCP 需要三次握手才能建立,而断开连接则需要四次握手。整个过程如下图所示:

TCP 连接断开工程示意

建立连接:三次握手

TCP 建立连接的过程简单来说,首先 Client 端发送连接请求报文,Server 段接受连接后回复 ACK 报文,并为这次连接分配资源。Client 端接收到 ACK 报文后也向 Server 段发生 ACK 报文,并分配资源,这样 TCP 连接就建立了。TCP 建立连接时,首先客户端和服务器处于 close 状态。然后客户端发送 SYN 同步位,此时客户端处于 SYN-SEND 状态,服务器处于 lISTEN 状 态,当服务器收到 SYN 以后,向客户端发送同步位 SYN 和确认码 ACK,然后服务器变为 SYN-RCVD,客户端收到服务器发来的 SYN 和 ACK 后,客户 端的状态变成 ESTABLISHED(已建立连接),客户端再向服务器发送 ACK 确认码,服务器接收到以后也变成 ESTABLISHED。然后服务器客户 端开始数据传输

TCP 三次握手示意

  • 第一次握手:Client 将标志位 SYN 置为 1,随机产生一个值 seq=J,并将该数据包发送给 Server,Client 进入 SYN_SENT 状态,等待 Server 确认。

  • 第二次握手:Server 收到数据包后由标志位 SYN=1 知道 Client 请求建立连接,Server 将标志位 SYN 和 ACK 都置为 1,ack=J+1,随机产生一个值 seq=K,并将该数据包发送给 Client 以确认连接请求,Server 进入 SYN_RCVD 状态。

  • 第三次握手:Client 收到确认后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack=K+1,并将该数据包发 送给 Server,Server 检查 ack 是否为 K+1,ACK 是否为 1,如果正确则连接建立成功,Client 和 Server 进入 ESTABLISHED 状态,完成三次握手,随后 Client 与 Server 之间可以开始传输数据了。

传输数据过程

超时重传

超时重传机制用来保证 TCP 传输的可靠性。每次发送数据包时,发送的数据报都有 seq 号,接收端收到数据后,会回复 ack 进行确认,表示某一 seq 号数据已经收到。发送方在发送了某个 seq 包后,等待一段时间,如果没有收到对应的 ack 回复,就会认为报文丢失,会重传这个数据包。

快速重传

接受数据一方发现有数据包丢掉了。就会发送 ack 报文告诉发送端重传丢失的报文。如果发送端连续收到标号相同的 ack 包,则会触发客户端的快速重传。比较超时重传和快速重传,可以发现超时重传是发送端在傻等超时,然后触发重传;而快速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传。

流量控制

这里主要说 TCP 滑动窗流量控制。TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。滑动窗可以是提高 TCP 传输效率的一种机制。

拥塞控制

滑动窗用来做流量控制。流量控制只关注发送端和接受端自身的状况,而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。考虑一下这样的场景:某一时刻网络上的延时突然增加,那么,TCP 对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的 TCP 连接都这么行事,那么马上就会形成“网络风暴”,TCP 这个协议就会拖垮整个网络。为此,TCP 引入了拥塞控制策略。拥塞策略算法主要包括:慢启动,拥塞避免,拥塞发生,快速恢复。

关闭连接:四次握手

关闭连接的四次握手过程可以概述为,设 Client 端发起中断连接请求,也就是发送 FIN 报文。Server 端接到 FIN 报文后,意思是说"我 Client 端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭 Socket,可以继续发送数据。所以你先发送 ACK,“告诉 Client 端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候 Client 端就进入 FIN_WAIT 状态,继续等待 Server 端的 FIN 报文。当 Server 端确定数据已发送完成,则向 Client 端发送 FIN 报文,“告诉 Client 端,好了,我这边数据发完了,准备好关闭连接了”。Client 端收到 FIN 报文后,“就知道可以关闭连接了,但是他还是不相信网络,怕 Server 端不知道要关闭,所以发送 ACK 后进入 TIME_WAIT 状态,如果 Server 端没有收到 ACK 则可以重传。“,Server 端收到 ACK 后,“就知道可以断开连接了”。Client 端等待了 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,我 Client 端也可以关闭连接了。Ok,TCP 连接就这样关闭了!

TCP 四次挥手

  • 第一次挥手:Client 发送一个 FIN,用来关闭 Client 到 Server 的数据传送,Client 进入 FIN_WAIT_1 状态。

  • 第二次挥手:Server 收到 FIN 后,发送一个 ACK 给 Client,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号),Server 进入 CLOSE_WAIT 状态。

  • 第三次挥手:Server 发送一个 FIN,用来关闭 Server 到 Client 的数据传送,Server 进入 LAST_ACK 状态。

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

由于 TCP 连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个 FIN 来终止这一方向的连接,收到一个 FIN 只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个 TCP 连接上仍然能够发送数据,直到这一方向也发送了 FIN。首先进行关闭的 一方将执行主动关闭,而另一方则执行被动关闭。

状态转移图

TCP 状态转移图

状态图详细解读:

1.CLOSED:起始点,在超时或者连接关闭时候进入此状态。

2.LISTEN:服务端在等待连接过来时候的状态,服务端为此要调用 socket,bind,listen 函数,就能进入此状态。此称为应用程序被动打开(等待客户端来连接)。

3.SYN_SENT:客户端发起连接,发送 SYN 给服务器端。如果服务器端不能连接,则直接进入 CLOSED 状态。

4.SYN_RCVD:跟 3 对应,服务器端接受客户端的 SYN 请求,服务器端由 LISTEN 状态进入 SYN_RCVD 状态。同时服务器端要回应一个 ACK,同时发送一个 SYN 给客户端;另外一种情况,客户端在发起 SYN 的同时接收到服务器端得 SYN 请求,客户端就会由 SYN_SENT 到 SYN_RCVD 状态。

5.ESTABLISHED:服务器端和客户端在完成 3 次握手进入状态,说明已经可以开始传输数据了。

以上是建立连接时服务器端和客户端产生的状态转移说明。相对来说比较简单明了,如果你对三次握手比较熟悉,建立连接时的状态转移还是很容易理解。

下面,我们来看看连接关闭时候的状态转移说明,关闭需要进行 4 次双方的交互,还包括要处理一些善后工作(TIME_WAIT 状态),注意,这里主动关闭的一方或被动关闭的一方不是指特指服务器端或者客户端,是相对于谁先发起关闭请求来说的:

6.FIN_WAIT_1:主动关闭的一方,由状态 5 进入此状态。具体的动作是发送 FIN 给对方。

7.FIN_WAIT_2:主动关闭的一方,接收到对方的 FIN-ACK(即 fin 包的回应包),进入此状态。

8.CLOSE_WAIT:接收到 FIN 以后,被动关闭的一方进入此状态。具体动作是接收到 FIN,同时发送 ACK。(之所以叫 close_wait 可以理解为被动关闭方此时正在等待上层应用发出关闭连接指令)

9.LAST_ACK:被动关闭的一方,发起关闭请求,由状态 8 进入此状态。具体动作是发送 FIN 给对方,同时在接收到 ACK 时进入 CLOSED 状态。

10.CLOSING:两边同时发起关闭请求时,会由 FIN_WAIT_1 进入此状态。具体动作是接收到 FIN 请求,同时响应一个 ACK。

11.TIME_WAIT:最纠结的状态来了。从状态图上可以看出,有 3 个状态可以转化成它,我们一一来分析:

a.由 FIN_WAIT_2 进入此状态:在双方不同时发起 FIN 的情况下,主动关闭的一方在完成自身发起的关闭请求后,接收到被动关闭一方的 FIN 后进入的状态。

b.由 CLOSING 状态进入:双方同时发起关闭,都做了发起 FIN 的请求,同时接收到了 FIN 并做了 ACK 的情况下,由 CLOSING 状态进入。

c.由 FIN_WAIT_1 状态进入:同时接受到 FIN(对方发起),ACK(本身发起的 FIN 回应),与 b 的区别在于本身发起的 FIN 回应的 ACK 先于对方的 FIN 请求到达,而 b 是 FIN 先到达。这种情况概率最小。