虽然书本上学过,但是太理论讲不出来,自己理解一下写写4.1 TCP 三次握手与四次挥手面试题 | 小林coding (xiaolincoding.com)
三次握手过程😀
过程图
总结性的理解
为了方便起见,我们简单的定义一下:
- 喂:这是一个打招呼的语句,类似打电话,在上图代表SYN
- 你好:这是一个代表确认收到的消息,就像打电话收到听到对方说话后的回应。在上图代表ACK
- 两者约定喂和你好的 数量需要对应,才是正确的接收,否则对方就是假冒者!说出 “滚!”
- 滚:代表拒绝或者关闭连接
于是整个三次握手的过程就变为了
客户端 | 时间 | 服务端 |
"喂?" | ↓ | |
↓ | "喂?你好!" | |
“你好!……” | ↓ | |
可以总结出一些特点: |
- 第三次握手是可以携带数据的,前两次握手是不可以携带数据的
- 序列号和确认号是保证TCP通信稳定性的关键因素
为什么是三次握手?不是两次
下面讨论一下两次握手的情况。在这种情况下,服务端听到了 喂? 就立马进入连接就绪状态;客户端听到服务端的"喂?你好!" 后也进入连接就绪状态。
- 三次握手才可以阻止重复历史连接的初始化(主要原因) 考虑一个场景,客户端先说了 喂? 因为网络太差了,服务端很久都没听到。
于是客户端又说了 喂喂?,这时网络恢复了(或者第一个喂终于传到了),服务端赶紧说了一声 你好,然后进入连接就绪状态听接下来讲什么
但是客户端认为你没有按照我们的约定说你好你好,于是认为他是假冒者,说出了滚。服务器听到后,只能无奈断开连接。
过了好久,服务端终于听到了喂喂?,赶紧说了一声 你好你好!,然后进入连接就绪状态听接下来讲什么。这次没出问题,客户端听到 - 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
为什么不是四次?
三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
小结
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用「两次握手」和「四次握手」的原因:
- 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
- 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
四次挥手过程😘
过程图
可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
为什么挥手需要四次?
再来回顾下四次挥手双方发 FIN
包的过程,就能理解为什么需要四次了。
- 关闭连接时,客户端向服务端发送
FIN
时,仅仅表示客户端不再发送数据了但是还能接收数据。 - 服务端收到客户端的
FIN
报文时,先回一个ACK
应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN
报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK
和 FIN
一般都会分开发送,因此是需要四次挥手。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL
是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL
字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN
报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
TIME_WAIT 过多有什么危害?
过多的 TIME-WAIT 状态主要的危害有两种:
- 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
- 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为
32768~61000
,也可以通过net.ipv4.ip_local_port_range
参数指定范围。