背景
当一台客户端和一台服务器进行通信时,TCP协议使用三次握手和四次挥手来建立和终止连接。
三次握手的作用
- 确认双方的发送和接收能力:在三次握手中,客户端和服务器需要交换彼此的序列号和确认号,以确认彼此的发送和接收能力正常,确保双方可以正常通信。
- 确认双方的同步连接状态:在三次握手的过程中,服务端和客户端会互相确认对方的初始序列号,以确保双方处于同步的连接状态,准备好正式传输数据。
四次挥手的作用
- 安全关闭连接:四次挥手确保了连接的安全关闭,双方可以在终止通信前完成数据交换和清理工作。
- 确保数据完整性:在四次挥手中,双方可以通过最后一次传输的数据来确认对方是否收到了所有数据,从而保证数据的完整性。
三次握手
为什么需要三次握手
即使进行N次握手,也无法百分之百地确保双方成功建立连接。这是因为一旦最后一次确认丢失,双方就陷入信息不对称的状态,即使之前的信息是对称的。成功建立连接的真正标志是双方都知道对方已经准备好传输数据,而至少需要三次握手才能实现这一点。
N次握手的案例分析
以 A 作为客户端,B 作为服务器端。
一次握手
那么 A 向 B 发送一个报文,然后双方都认为连接已经建立,这等同于使用无连接的 UDP,毫无意义。
两次握手
A 向 B 发送 SYN 报文,B 向 A 发送一个 ACK 报文(可能还包含 SYN 标志位),这时 B 已经知道 A 想要建立连接,双方的信息已基本对称,然而如果 B 到 A 的报文段丢失了,那么 A 就无法确定 B 是否收到了自己的连接请求,A 的状态成为未知,B 也知道了 A 的这种情况。
三次握手
因此需要第三次握手,即 A 向 B 发出 ACK 报文,这时双方都知道对方已经准备好传输数据(之前的时间点准备好,当前状态依然是不对称)。
三次握手解决-网络数据延迟
如果存在数据包延迟到达的情况,就会出现"已失效的连接请求报文段",比如 A 向 B 发送的连接请求报文延迟到达 B,B 误以为这是一个新的连接请求,然后接受并发送 ACK 报文。如果是两次握手,B 此时就会进入建立状态,但这是一个错误的状态,因为 A 早已放弃了这个连接。
问题总结
无论进行多少次握手,都无法百分之百地确保成功建立连接,因为最后一次报文可能会丢失,或者出现延迟等各种情况。成功进行三次握手只能说明双方现在以相当高的概率可以进行正常通信。
四次挥手
四次挥手实际上就是两个FIN报文和两个ACK报文,这四个报文必不可少。
为什么不能学习三次握手将FIN和ACK合二为一?
A-TCP发送关闭报文
在TCP连接中,当一方(称为A)没有数据要发送时,会向另一方(称为B)发送一个FIN报文,而B必然会回复一个ACK报文。一旦B接收到FIN报文,它会通知上层应用程序。
B-TCP接收关闭报文
鉴于上层应用程序可能尚未发送完所有数据,B在这种情况下不应立即发送自己的FIN报文,即便数据已经全部发送完毕,B也不应该将FIN和ACK合二为一。
最佳做法是先发送ACK报文通知对方,然后在合适的时机才发送自己的FIN报文。
服务器主动端口
在TCP协议的C/S模式下,当TCP客户端想断开的时候,不能用 shutdown和closesocket与TCP服务器断开,只有让TCP服务器端主动断开(TCP 客户端被动断开),TCP 客户端的端口才能立刻被释放。
注意:中断连接端可以是Client端,也可以是Server端。
详细分析四次挥手
Client端-FIN阶段
假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。
Client端-FIN_WAIT阶段
Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。
Client端-TIME_WAIT阶段
Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。
Client端-CLOSED阶段
Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。
注意:在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送。TIME_WAIT状态中所需要的时间是依赖于实现方法的。典型的值为30秒、1分钟和2分钟。等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放。