TCP 头部格式
- 确认 ACK:当 ACK=1 时说明字段是有效的,否则无效。TCP 规定建立连接后的所有报文段 ACK 都为 1。
- 同步 SYN:在建立连接时用来同步序列号。
- SYN=1,ACK=0 表示连接请求报文段。
- SYN=1,ACK=1 表示同意连接。
- 终止 FIN:当 FIN=1 说明发送方的报文发送结束,要求释放连接。
- 窗口:窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
TCP 的三次握手
过程
假设 A 为客户端,B 为服务器端。
- 第一次握手,客户端向服务器发送 SYN=1 的报文,同时客户端进入 SYN-SENT 状态,等待服务器确认报文;
- 第二次握手,服务器向客户端发送 SYN=1, ACK=1 的报文(说明服务器同意连接),同时服务器进入 LISTEN 状态;
- 第三次握手,客户端收到 SYN=1, ACK=1 的报文后向服务器发送 ACK=1 的确认包,同时客户端进入 ESTABLISH 状态。当服务器收到客户端发送的 ACK 后,也会进入 ESTABLISH 状态;
为什么需要三次握手?
三次握手可以确认服务器和客户端的收包和发包能力
- 第一次握手:服务器可以确认客户端的发送能力、服务器的接收能力;客户端可以确认客户端的发送能力。
- 第二次握手:服务器可以确认服务器的发送能力;客户端可以确认客户端的接收能力,服务器的发送和接收能力。
- 第三次握手:服务器可以确认客户端的接收能力;
序列号可靠同步
如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。
阻止重复历史连接的初始化
客户端由于某种原因发送了两个不同序号的 SYN 包,我们知道网络环境是复杂的,旧的数据包有可能先到达服务器。如果是两次握手,服务器收到旧的 SYN 就会立刻建立连接,那么会造成网络异常。
假设客户端发出的第一个 SYN 包并没有丢失,而是在某个网络节点长时间的滞留,以致延误到连接释放以后的某个时间点才到达服务器。本来这是一个早已失效的报文段,但服务器收到失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么服务器发出确认报文后就已进入 ESTABLISHED。客户端因为已进入 CLOSED 状态,所以不会向服务器发送数据。但服务器会一直等待客户端发来数据,导致浪费服务器可用的连接资源。
如果是三次握手,服务器需要回复 SYN+ACK 包,客户端会对比应答的序号,如果发现是旧的报文,就会给服务器发 RST 报文,直到正常的 SYN 到达服务器后才正常建立连接。所以三次握手才有足够的上下文信息来判断当前连接是否是历史连接。
TCP 四次挥手
过程
- 第一次挥手,客户端向服务器发送 FIN=1 的报文;客户端进入 FIN_WAIT_1 的状态。
- 第二次挥手,服务器收到后发出 ACK=1 的报文,此时 TCP 处于半关闭状态,服务器可以向客户端发送数据,但是客户端不可以再向服务器发送数据。此时服务器进入 CLOSE_WAIT 的状态,客户端接收到服务器端发送的 ACK 后,进入了 FIN_WAIT_2 状态。
- 第三次挥手,服务器数据发送完毕后,向客户端发送 FIN=1 的包,半关闭状态下服务器可能又发送了一些数据。服务器此时进入了 LAST_ACK 状态。
- 第四次挥手,客户端收到服务器的 FIN 包后,发出 ACK=1,ack=w+1 的确认包,此时客户端就进入了 TIME_WAIT 状态。注意此时 TCP 连接还没有释放,必须经过 2MSL 后,才进入 CLOSED 状态。而服务器端收到客户端的确认包 ACK 后就进入了 CLOSED 状态,所以服务器结束 TCP 连接的时间要比客户端早一些。
为什么需要四次挥手?
在客户端发送断开连接请求后,服务器可能还有数据需要传输。在握手的时候,服务器将 ACK 和 SYN 一起发送,但是在挥手时,服务器不能将 ACK 和 FIN 一起发送,服务器可能还有数据需要传输,一起发送会导致数据传输失败,所以要先确认 ACK,然后服务器没有数据需要传输时再发送 FIN。
TIME_WAIT 状态为什么需要等待 2MSL?
确认服务器收到 FIN 包
MSL 指的是报文在网络中最大生存时间。在客户端发送对服务器端的 FIN 的确认包 ACK 后,这个 ACK 包是有可能不可达的,服务器端如果收不到 ACK 的话需要重新发送 FIN 包。所以客户端发送 ACK 后需要留出 2MSL 时间(ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回)等待确认服务器端确实收到了 ACK 包。也就是说客户端如果等待 2MSL 时间也没有收到服务器端的重传包 FIN,说明可以确认服务器已经收到客户端发送的 ACK。
避免新旧连接混淆
在客户端发送完最后一个 ACK 报文段后,在经过 2MSL 时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。有些自作主张的路由器会缓存 IP 数据包,如果连接重用了,那么这些延迟收到的包就有可能会跟新连接混在一起。