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 。然后服务器客户 端开始数据传输
第一次握手: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 连接就这样关闭了!
第一次挥手: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 。首先进行关闭的 一方将执行主动关闭,而另一方则执行被动关闭。
状态转移图
状态图详细解读:
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 先到达。这种情况概率最小。