TCP为什么握手要3回,挥手4次?深入浅出给你图解
哈喽,大家好呀,我是呼噜噜,好久没有更新了,今天我们来聊聊一下TCP三次握手和四次挥手,这个非常经典的问题。我们先来回顾一下,什么是TCP协议?
什么是TCP协议
TCP协议,全名叫Transmission control protocol
,即传输控制协议,基于OSI七层模型中的传输层
,它是一种提供**⾯向连接的、可靠的、基于字节流的**端到端通信服务 的传输协议,它的传输单位为报⽂段segment
在如今互联网时代,我们每天刷短视频、打游戏、和家人打视频电话,甚至于任何网络的动作,能保持如此流畅的使用体验,背后都离不开TCP的默默工作
。我们都知道互联网的底层,就是依托于 全世界各个地方各种型号的海量的网络设备互联,然后现实中存在电磁干扰、信号衰减,物理线路老化,路由器故障等各种各样的问题,所以互联网天生就是不可靠的。
而TCP就是要在不可靠的互联网上,通过三次握手四次挥手等一系列机制,"尽可能地"提供可靠的端到端的传输服务。
TCP报文的首部格式
要想了解三次握手四次挥手
这一非常经典的机制,我们得先了解一下TCP报文的首部格式,TCP首部
是TCP报文段的重要组成部分,它负责在数据传输过程中提供必要的控制信息
,包含了建立连接、可靠数据传输、流量控制和连接终止所需的所有控制信息。其长度通常为20字节
(不包含选项字段),最大可达60字节
(包含选项字段)
TCP首部包含了多个字段,每个字段都有其特定的功能和意义,它们共同确保了TCP连接的可靠性和数据的正确传输。
- 源端口号(Source Port)和目的端口号(Destination Port):各占16位,范围:
0-65535
,用于标识发送和接收数据的应用程序。 - 序号(Seq):占4字节,标识数据的顺序,保证传输数据的顺序性和可靠性(用于确认丢失或乱序的报文段)
- 确认序号(Ack):占4字节,表示接收方期望收到的下一个字节的序列号,用于确认已成功接收的数据,告诉发送方自己接收到了哪些数据。
确认序号应当是上次已成功收到数据字节序号加 1。只有 ACK 标志为 1 时确认序号字段才有效。
- 首部长度(Header Length):占4位,这里更准确地讲是
数据偏移
,表示TCP首部长度,因为首部的长度是可变的,主要是由于选项字段的存在;首部长度 = 数据偏移值 * 4 字节
- 保留(Reserved):占6位,暂未使用,保留用于未来扩展
- 控制位(Control Bits):包括URG、ACK、PSH、RST、SYN和FIN等标志,用于控制TCP连接的状态和数据传输的行为,必须要理解控制位,它将是我们理解TCP三次握手的基础
- URG (Urgent Bit):值为 1 时,紧急指针生效
- ACK (Acknowledgment Bit):值为 1 时,确认序号的字段生效,表示数据信息确认,TCP规定除了最初建立连接时的 SYN 包之外该位必须设置为1
- PSH (Push Bit):接收方应尽快将这个报文段交给应用层
- RST (Reset Bit):发送端遇到问题,想要重建连接
- SYN (Synchronize Bit):同步序号,值为 1 时,表示希望建立连接
- FIN (Finish Bit):值为 1 时,表示希望关闭连接
- 窗口(Window):占2字节,用于流量控制,指示发送方可以发送的数据量
- 检验和(Checksum):占2字节,差错检测,用于验证数据完整性
- 紧急指针(Urgent Pointer):占2字节,仅在URG标志位为1时有效,指示紧急数据的结束位置
- 选项(Options):这一部分是可选字段,也就是非必须字段;长度可变,最长可达40字节,用于支持各种TCP功能,如最大报文段长度(MSS)、窗口扩大、时间戳和选择确认等。
- 有效数据部分 (Data):这部分不是必须的,比如在建立和关闭 TCP 连接的阶段,双方交换的报文段就只包含 TCP 首部。
三次握手
首先我们得知道TCP是全双工通信,它运行允许数据同时在两个方向上传输(两个信道),即允许同时进行双向传输,另一方面要求收发双方都有独立的接收和发送能力
这就意味着,客户端既能主动打开连接也能被动打开连接(变成了服务端),举个通俗点的例子,客户端和服务端,就像我们人类,2个人之间,可以同时主动讲话,还能够(在讲话的同时)彼此都听得到对方的声音
tcp的核心特征之一就是面向连接
,这意味着在数据传输之前,客户端与服务端端必须先建立连接。而这个客户端与服务端端建立连接的过程,我们称之为"三次握手"
那什么是三次握手呢?为了方便大家的理解,我们先来看一张图
假设客户端是主动打开方,服务端是被动打开方,一开始客户端和服务端都处于关闭状态CLOSED
。服务端,比如服务端中的进程先创建传输控制块TCB,服务端也就会处于监听状态LISTEN
,主动监听某个端口
第一次握手:客户端主动发起连接请求,将SYN控制位设置为1
,表示 SYN
报文;并选择一个**随机序号**** **seq = x
。发送 SYN 数据包后,客户端此时进入了同步已发送状态 SYN-SENT
,等待服务端的确认
SYN
报文是不包含应用层的数据,但要消耗掉一个序号
第二次握手:当服务端收到客户端的 请求连接的 SYN
报文后,如果同意建立连接,会选择一个随机序号seq = y
,将SYN和ACK控制位设置为1
,并将确认号ack设置为x + 1
。发送确认报文
后,服务端此时进入同步已收到状态 SYN-RECEIVED
。该报文也不含有应用层的数据,同时也会消耗掉一个序号
当客户端收到服务端的确认后,客户端进入连接已建立状态ESTABLISHED
;它认为此时连接已建立,即对于客户端来说,第二次握手后TCP连接已建立
第三次握手:当客户端收到服务端的确认后,客户端还要向服务端发生最后一个应答报文
,将ACK位设置为1
,确认号ack=y+1
,序号seq=x+1
;该报文可以携带应用层的数据
,如果不携带数据则不消耗序号,后续报文的序号仍为seq=x+1
。
当服务端收到该报文后,服务端进入连接已建立状态ESTABLISHED
。对于服务端来说,在第三次握手后TCP连接已建立。
这个时候大家就会有疑惑,三次握手,我都看懂了,但2次就不行吗?那4次握手岂不是更好?
我们回顾一下,三次握手做了什么:
第一次握手,客户端发送了"我想要和你建立连接"的SYN报文
。
第二次握手,当服务端收到报文后,如果同意连接,则发送 SYN + ACK
报文,表示“我收到了你的 SYN,我同意连接,我的初始序列号是y,并且我期待你下一个发送序号是x+1”;第二次握手让客户端确认了,服务端有收发能力(接收报文和发生报文),所以客户端进入了连接已建立状态ESTABLISHED
。
第三次握手,客户端收到SYN + ACK
报文,发送ACK
报文给服务端,表示“我收到了你的 SYN+ACK报文,我确认连接建立,我期待你下一个发送序号是y+1”;第三次握手让服务端确认了,客户端也有收发能力,所以服务端进入了连接已建立状态ESTABLISHED
三次握手确认了双方都具有收发
报文的能力,另外三次握手还能防止历史连接或者已失效的连接初始化
,造成的重复连接或者资源浪费。
在现实中,某个客户端连接请求,可能因为网络的原因,没有丢失,反而在网络中滞留了很久;当服务端收到这个历史 SYN
,会认为它是一个新连接请求,于是发送 SYN+ACK
并进入 SYN_RECEIVED
状态,如果只有2次握手,由于客户端当前并没有发起这个请求,客户端一般会忽略这个请求,但是服务端并不知道,它还傻乎乎地等待客户端发送消息,浪费服务资源。所以这个时候需要第3次握手,即使服务端收到这个历史连接,并发出了SYN+ACK
报文,由于客户端不会对这个历史SYN+ACK
报文,进行 ACK
确认。这个时候服务端由于长时间收不到第三次握手的确认报文,会超时重传并最终放弃连接请求
那么4次握手呢,3次握手是最小且必要的次数,即使再多次实现的效果也和3次握手一样,所以无需使用更多的通信次数
四次挥手
建立一个TCP
连接需要3次握手
,而终止一个连接需要经过4次挥手
。这是因为TCP连接是全双工的,且TCP支持半关闭(Half-Close),数据能同时在两个方向上独立传输,所以每个方向上的连接,必须单独进行关闭
那什么是四次挥手呢?结合下图,快速理解
假设主动关闭方是客户端,被动关闭方是服务端,此时客户端和服务端都处于ESTABLISHED
状态
第一次挥手:客户端主动发送 FIN
报文给服务端,请求 关闭TCP连接,并停止发送数据;其中报文,FIN 标志位设置为1,序号字段 seq = x
(它等于前面以传送过的数据的最后一个字节的序号加1
)然后客户端会进入 FIN-WAIT-1
状态,等待来自服务端的确认报文。
需要注意一下,TCP规定:FIN报文段即使不携带数据,也消耗掉一个序号!!
第二次挥手:服务端收到 FIN
报文后,发回确认报文,确认号是ack = x + 1
,ACK = 1,自己的序号 seq = y
,然后进入关闭等待状态CLOSE-WAIT
。客户端收到这个 ACK
后,进入 FIN_WAIT_2
状态,等待服务端发送连接释放FIN
的报文
服务端(服务器)还会通知通知应用进程,对方已经释放连接;也就是说此时,客户端->服务端
方向上的TCP连接已经关闭,但服务端->客户端
方向上的TCP数据通道仍然会存在一段时间,客户端还能收到来自服务端的数据,直到收到 FIN
报文段。
第三次挥手:当服务端发送完所有数据后,会向客户端发送 FIN
报文,请求关闭它这个方向的连接,各字段FIN = 1,ACK = 1
,服务端还必须重复上次已发送过的确认号ack = x + 1
,假定自己的序号seq为z
,服务端进入最后确认状态LAST_ACK
,等待客户端的确认
第四次挥手:当客户端收到服务端的FIN
报文,发送ACK
报文进行确认,ACK置为1,确认号ack=z+1,seq=x+1
,客户端进入时间等待状态TIME_WAIT
。需要注意的是,此时连接还没有释放,必须经过时间等待计时器设置的时间2MSL后,客户端才能进入 CLOSED
状态。
MSL,Maximum Segment Lifetime叫做最长报文段寿命,常用值有30秒、1分钟和2分钟,RFC793 建议设在两分钟,但TCP允许不同的实现可以根据具体情况使用更小的MSL值。
服务端收到这个最终的ACK报文后,确认连接关闭,随即进入 CLOSED
状态
这里可能就有人要问了客户端在第4次挥手时,为啥要等待2MSL后才能进入 CLOSED
状态?
这主要是因为,第4次挥手的ACK报文有可能会丢失
,等待2MSL
就是,当这个 ACK
报文 丢失,服务端会超时重传 FIN报文,客户端在TIME_WAIT
状态期间,如果收到了重传的 FIN 后,会再次发送 ACK,以此确保第4次挥手的ACK报文到达服务端
另一个作用,就是防止已失效的连接请求消失,等待足够长的时间(2MSL),确保网络中所有属于这个连接的报文都已过期消失,避免干扰后续可能使用相同端口的新连接。
还有一个常见的面试题,为什么 TCP 关闭连接为什么要四次而不是三次?第二次挥手和第三次挥手能不能合并到一起?
TCP是全工的,关键在于第二次挥手时,当服务端收到客户端的 FIN
报文(第一次挥手)时,它只是知道客户端不再发送数据,但服务端可能还有数据需要处理并传输给客户端,所以不能马上关闭连接。服务端需要先发送 ACK
确认收到了客户端的关闭请求(第二次挥手),然后等待应用层处理完数据并决定关闭,最后再发送自己的 FIN
(第三次挥手),然后客户端再做出应答,因此一共需要四次挥手
四次挥手是保证了双方都知道并且都已经断开连接,确保数据在TCP关闭过程中能够被完整传输,同时也允许延迟的数据包在关闭后仍然能够被接收
但有些面试官可能比较有经验,会特别强调在现实工作中,第二次挥手和第三次挥手,真的不存在合并的情况吗?
其实我们抓包的话,会发现在某些场景下,第二次挥手和第三次挥手"合并"到一起了,当第一次挥手后,服务端收到客户端 FIN
时,恰好没有数据要发送了,同时开启了TCP延迟确认机制
为了提升TCP传输效率,TCP延迟确认机制,当遇到没有携带数据的ACK报文,将第二次挥手的 ACK
延迟等待, 等后面的第三次挥手 FIN
报文过来,合并成一个 ACK + FIN
报文一起发送,这样看起来就像“三次挥手”了。这种属于特殊情况,我们还是得记住,TCP的规范就是四次挥手
尾语
TCP通过三次握手和四次挥手机制,都是为了在不可靠的IP网络层之上,不可靠信道上,"尽可能地"提供可靠的端到端的传输服务。通过确认机制、序列号、延迟机制和状态管理,确保了数据传输的可靠性、有序性和连接管理的正确性
大家觉得,即使有三次握手和四次挥手等一系列的机制,TCP连接真的能“完全可靠”吗?实际上它并不是"银弹",但不管如何,这些机制至少可以确认连接是"基本可用"的,所以TCP中的"握手3回,挥手4次"不愧是是软件工程中多方"权衡"的经典案例
作者:小牛呼噜噜
本文到这里就结束啦,感谢阅读,关注同名公众号:小牛呼噜噜,防失联+获取更多技术干货