一、TCP 的流量控制
TCP 使用==窗口机制==进行流量控制
什么是窗口?
连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送另一端。
接收方发送的确认信息中,包含自己剩余的缓冲区尺寸。
剩余缓冲区空间的数量叫做窗口
TCP 的流控过程(滑动窗口)
二、TCP 格式报文
- 序号:seq 序号,占 32 位,用来标识从 TCP 源端向目的端发送的字节流,发起方发送数据时对此进行标记。
- 确认序号:ack 序号,占 32 位,只有 ACK 标志位为 1 时,确认序号字段才有效,ack = seq + 1。
- 标志位:共 6 个,即 URG、ACK、PSH、RST、SYN、FIN 等,具体含义如下:
- URG(urgent):紧急指针有效
- ACK(acknowledgement):确认序号有效
- PSH(push):接收方应该尽快将这个报文交给应用层
- RST(reset):重置链接
- SYN(synchronus):发起一个新连接
- FIN(finish):释放一个链接
注意:
- 不要将确认序号 ack 和标志位中的 ACK 搞混了
- 确认方 ack = 发起方 req + 1,两端配对。
TCP协议中的三次握手和四次挥手图解
TCP (Transmission Control Protocol) 传输控制协议
建立 TCP 需要三次握手才能建立,而断开连接需要四次挥手
==各状态的意义如下:==
- LISTEN - 侦听来自远方 TCP 端口的连接请求
- SYN_SENT - 在发送请求后等待匹配的连接请求
- SYN_RCVD - 在收到和发送一个连接请求后等待对连接请求的确认
- ESTABLISHED - 代表一个打开的连接,数据可以传送给用户。
- FIN_WAIT-1 - 等待远程 TCP 的连接中断请求,或等待先前的中断请求的确认。
- FIN_WAIT-2 - 从远程 TCP 等待连接中断请求。
- CLOSE-WAIT - 等待从本地用户发来的连接中断请求
- CLOSING - 等待远程 TCP 对中断请求的确认
- TIME-WAIT - 等待足够时间以确保远程 TCP 接收到连接中断请求的确认
- CLOSED - 没有任何连接状态
三次握手
- 第一次握手:建立连接时,client 发送 SYN 包(==SYN = 1,seq = j==) 到 server ,并进入 SYN_SEND 状态,等待 server 确认。
- 第二次握手:server 收到 SYN 包,由标志位 SYN = 1 知道 client 请求建立连接,server 确认 client 的 SYN,即 server 将设置标志位 ==ACK = 1,以及 ack=j+1==,同时自己也发送一个 SYN 包 (==SYN=1,seq=k==),即 SYN + ACK 包,此时 server 进入 SYN_RCVD 状态。
- 第三次握手: client 收到 server 的 SYN+ACK 包,检查 ack 是否为 j+1,ACK 是否为 1 ,如果正确,则向 server 发送确认包 ACK(==ACK=1,ack=k+1==),Server 检查 ack 是否为 k+1,ACK 是否为 1,如果正确,此包发送完毕,client 和 server 进入 ESTABLISHED 状态,完成三次握手。client 与 server 开始传送数据。
==确认号 ack :其数值等于发送方的发送序号 (seq) + 1 (即接收方期待接收的下一个序列号)==
四次挥手
由于 TCP 连接是全双工的,因此,每个方向都必须单独关闭,这个原则是 当一方完成它的数据发送任务后,就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这个方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
1. 第一次挥手:Client 发送一个 FIN ==(fin = a,ack = b,ACK=1,FIN = 1)==,用来关闭 Client 到 Server 的数据传送,Client 进入 FIN_WAIT_1 状态。
2. 第二次挥手:Server 收到 FIN 后,发送一个 ACK 给 Client ,确认序号 ack 为 收到序号 + 1 (与 SYN 相同,一个 FIN 占用一个序号)即 ==ack = a+1,ACK = 1== ,Server 进入 CLOSE_WAIT 状态。(服务器读通道关闭) (客户端关闭写通道)
3. 第三次挥手: Server 发送一个 FIN (==fin = c,ack = a+1,ACK = 1,FIN = 1==),用来关闭 Server 到 Client 的数据传送,Server 进入 LAST_ACK 状态。
4. 第四次挥手: Client 收到 FIN 后,Client 进入 TIME_WAIT 状态,(客户端关闭读通道) 接着发送一个 ACK (==ack = c+1 ,ACK = 1==)给 Server ,确认序号为收到序号+1 。(服务端关闭写通道)
假设 Client 端发起中断连接请求,也就是发送 FIN 报文,Server 端接收到 FIN 报文,意思是 Client 对 Server 说 “我 Client 端没有数据要发给你了”,但是此时如果 Server 还有数据没有发送完成,则不必急着关闭 Socket ,可以继续发送数据。所以 Server 端先发送 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 端就可以关闭连接了。此时,TCP 连接就关闭了。
==简单来说是“先关读,再关写”==
以客户端发出关闭连接为例
1. 服务器读通道关闭
2. 客户端写通道关闭
3. 客户端读通道关闭
4. 服务器写通道关闭
关闭行为是发起方数据发送完毕后,给对方发送一个 FIN(finish)数据段,直到接收到对方发送的 FIN ,且对方收到了接收确认 ACK 之后,双方的数据通信才完全结束。过程中每次接收都需要返回确认数据段 ACK。
第一阶段 客户机发送完数据之后,向服务器发送一个 FIN 数据段,序列号为 i
1. 服务器收到 FIN(i) 后,返回确认段 ACK,序列号为 i+1,==关闭服务器读通道==;
2. 客户端收到 ACK(i+1) 后,==关闭客户端写通道==。
(此时,客户端仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据)
第二阶段 服务器发送完数据之后,向客户端发送一个 FIN 数据段,序列号为 j ;
3. 客户端收到 FIN(j) 后,返回确认段 ACK,序列号为 j+1 ,==关闭客户端读通道==;
4. 服务器收到 ACK(j+1) 后,==关闭服务器写通道==
这是标准的TCP关闭两个阶段,服务器和客户机都可以发起关闭,完全对称。
为什么连接的时候是三次握手,关闭却要四次挥手呢?
因为当 Server 收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文用来应答,SYN 报文是用来同步的。但是关闭连接时,当 Server 端接收到 FIN 报文时,很有可能并不会立即关闭 Socket ,所以只能先回复一个 ACK 报文,告诉 Client 端,“你发的 FIN 报文 我已经收到了,只有等到我的 Server 端所有的报文都发送完了,我才能发送 FIN 报文”,因此不能一起发送。所以需要四次握手。
为什么 TIME_WAIT 状态需要经过 2MSL (最大报文段生存时间) 才返回到 CLOSED 状态。
有两个原因:
1. 保证 TCP 协议的全双工连接能够可靠关闭
2. 保证这次连接的重复数据段从网络中消失
解释第一个原因:
如果 Client 直接 CLOSED,那么由于 IP 协议的不可靠性或者其他网络原因,导致 Server 没有收到 Client 最后回复的 ACK,那么 Server 就会在超时之后继续发送 FIN,此时由于 Client 已经 CLOSED 了,就找不到与重发的 FIN 对应的连接,最后 Server 收到 RST 而不是 ACK,Server 就会以为是连接错误把问题报告给高层。这种情况虽然不会造成数据丢失,但是却导致 TCP 协议不符合可靠连接的要求。所以 Client 不是直接进入 CLOSED,而是要保持 TIME_WAIT,当再次收到 FIN 的时候,能够保证对方收到 ACK,最后正确地关闭连接。
解释第二个原因:
如果 Client 直接 CLOSED,然后又向 Server 发起一个新连接,我们不能保证这个新连接与刚关闭连接的端口是不同的,也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才会到达 Server ,由于新连接和老连接的段口是一样的,又因为 TCP 协议判断不同连接的依据是 socket pair ,于是,TCP 协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了,所以 TCP 连接还要在 TIME_WAIT状态等待 2倍 MSL,这样可以保证本次连接的所有数据都从网络中消失。
(按道理,四个报文都发送完毕,我们就可以进入 CLOSED 状态了,但是我们必须假象网络是不可靠的,有可能最后一个 ACK 丢失,所以,TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文。)