前言

TCP是一个传输层协议,它的特点是面向连接、可靠传输、全双工。它的握手与挥手是非常值得让人去讨论的,我们学习TCP握手与挥手的时候往往会遇到以下几个问题:

为什么要这么设计?

两次握手行不行?

三次挥手行不行?

为什么有TIME_WAIT?

CLOSE_WAIT是什么?

………………

这些问题也曾让我苦恼,我记得,当时只是为了应试匆忙将其背了,其中的精髓没有理解深刻,总是记了又忘。直到今天,我再回头看这握手与挥手的过程,我发现,这些机制都是基于非常朴素的思想去设计的,自己不需要去记忆这些看似晦涩的东西。下面是我的总结。

TCP的握手

下面是一个TCP标准的握手流程图。

TCP握手过程

引用其他博客:

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

抛开这些复杂的流程说明,我们知道,TCP连接的建立起码需要考虑以下两个问题:

  1. 数据在传输中存在的数据延时或丢失问题

  2. 两个端系统(即Client 与 Server)中都需要知道对方已准备好了,且都能够发送与接收数据(即满足双工)。

对于第一个问题,数据的丢失还好解决,假如数据卡在网络链路中怎么办?

客户端:对于发起握手的客户端,它不知道到底是丢了还是卡住了,一旦本地的计时器超时了,它就会重新再发一次建立连接的请求。

服务端:对于接收握手请求的服务端来说,它可能会接收一次SYN请求,也可能会因网络拥塞接收到两次或多次SYN请求。

如果只有两次握手,即服务端在接收到客户端发来的 SYN 后就开始分配资源、建立连接。那么,在网络拥塞(但不丢包)的情况下,客户端为了建立一个连接,可能会因为超时重发了多个SYN。多个SYN到达服务端后,服务端会为每个到达的SYN分配资源、建立连接,如此就产生了资源的浪费。

对于第二个问题,其实很好理解,TCP连接的过程很像自己投简历的过程:

A: 你好,B,这是我的简历

B:你好,A,欢迎投递简历,请问你什么时间有空,咱们定一下面试时间。

A:收到,B,我…………………………。

这样,A和B都确定了对方的意向,接下来就可以继续商量面试相关的事情了。

TCP的握手非常像上面投简历的过程,有了这个印象,TCP握手的过程会理解的更深一些。

我们知道,建立TCP连接,肯定要满足全双工的特性,即客户端与服务端都能相互发送数据。在TCP中,想要发送信息,只知道自己要发的数据的序号是不够的,还需要参考对方响应的ACK(中的确认号)。

我们再看以下握手的流程图,在第二次握手时,服务端响应了SYN+ACK,还没有获得客户端ACK的响应,此时,服务端缺少必要信息的信息,也不知道对方的状态,因而无法达到建立全双工连接的条件,所以二次握手就建立连接不可行。

TCP的挥手

再贴一个四次挥手的流程图

TCP四次挥手

直接说“客户”与“服务器”不恰当 ,因为断开连接可以从任何一端主动发起。在这里我们不妨定义客户端是主动发起挥手的一端。

四次挥手过程看起来比三次握手复杂,但它还是与握手非常类似的过程,只是握手的时候服务端 SYN+ACK 一块响应了,挥手时服务端先发的 ACK,后又发的 FIN(可以注意第三次挥手发的 FIN + ACK 的确认号 和 第二次挥手时发的ACK确认号一致的)。

为什么这么设计,为什么和握手不同?

答案是:TCP是一个双工的协议。

再举一个不太准确的例子,当你和一个人交谈的时候,突然你有急事,需要中断对话:

A: 我有些急事,咱们之后再聊

B: 好的,我还有几句要说

B:…………,我讲完了

A:好了,我撤了

这还是半双工的情况,在全双工的情况下,客户端发出挥手时,服务端也许还在向客户端发送数据。

因此,客户端发出挥手时,服务端就先发一个ACK响应,表明了服务端知道要断开链接了,但是还有一点数据要发,此时,客户端进入了FIN_WAIT_2 , 服务端进入了 CLOSE_WAIT, 等到数据发完了,服务端就在发一个FIN+ACK 表明服务端数据发完了,这时,客户端知道服务端的数据发完了,回了个ACK,准备释放资源了,但是还不能立即释放,此时,客户端进入了TIME_WAIT状态再等个2MSL后才释放。

为什么此时客户端还不释放资源,直接进入CLOSED呢?

因为考虑到了网络拥堵。无论是第几次挥手还是第几次握手,都要考虑这个问题

如果此时客户端直接关闭了,那么,假如第四次挥手的 ACK 服务端没有收到,那么服务端不知道客户端是否已经接收到了FIN+ACK,所以处于LAST_ACK状态的服务端需要重新发送FIN+ACK,若这个FIN+ACK发到了客户端,客户端不响应,那么服务端会不断重试,直到最后超时才进入CLOSED状态。

在设置了 TIME_WAIT 后,如果客户端在此状态时接收到了服务端发来的FIN+ACK, 客户端会重新向服务端发送 ACK。

总结

只要考虑到网络拥堵、以及协议的全双工特性,就会发现TCP的握手与挥手的思想还是非常简单且合乎常理的。

引用

TCP的三次握手与四次挥手理解及面试题(很全面)_lucky_jun-CSDN博客_tcp三次握手四次挥手

浅谈 TCP 四次挥手_yeweilei的博客-CSDN博客_tcp挥手

tcp断开连接为什么是4次挥手_蜜汁程序员的博客-CSDN博客