目录
[控制位(也就是图中的Code bites):](#控制位(也就是图中的Code bites):)
[❓那为什么要有 TIME_WAIT 状态?](#❓那为什么要有 TIME_WAIT 状态?)
今天是2026年第一天,新的起点,祝大家在新的一年身体健康,技术之路稳定向前,收获满满!
😍😍😍
⏮回顾
我们先回顾一下什么是TCP,什么是UDP?
TCP是面向连接的 、可靠的 、基于字节流 的传输层通信协议,其核心目标是保证数据从源端到目的端的完整、有序、无丢失、无重复;
而UDP是无连接的 、不可靠的 、基于数据报 的传输层通信协议,核心目标是以最小的开销实现高速数据传输 ,不保证数据的可靠交付。
㊙️TCP与UDP的头部格式
TCP的头部格式
TCP头部格式
序列号:
在建立连接的时候由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认号:
表示接收端期望收到的下一个字节 的序号,同时确认已成功接收到该序号之前的所有字节 。用来解决丢包的问题。
控制位(也就是图中的Code bites):
**1️⃣URG:**表示紧急指针字段有效,数据段中包含 "紧急数据"
2️⃣ACK :该位为 1 时,「确认号」的字段变为有效,并且TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1
**3️⃣PSH:**表示接收端应立即将数据交给应用层,无需等待缓存填满4️⃣RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接
5️⃣SYN :该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定
6️⃣FIN :该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段
窗口大小:
表示接收端的接收窗口大小 (以字节为单位),用于实现 TCP 的流量控制 。
核心逻辑:接收窗口大小 = 接收端当前可用的缓存空间的大小,发送端的发送窗口不能超过接收端的接收窗口,这样可以避免接收端缓存溢出。
UDP的头部格式
UDP头部格式
源端口、目的端口:
主要是告诉UDP协议要将数据报发送给哪个进程
UDP长度:
该字段保存了 UDP 首部的长度跟数据的长度之和
校验和:
校验和是为了提供可靠的 UDP 首部和数据而设计,防止收到在网络传输中不完整的 UDP 包
📝TCP的三次握手
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。
下面是示意图:

一开始,服务端和客户端都是处于 CLOSED 状态 ,先是服务端开始监听某个端口,从而处于 LISTEN状态;
1️⃣第一次握手:
这个时候,就有客户端想要与服务端建立连接进行通信,客户端会随机初始化序列号,就比如图中假设为了x,再将此序号置于 TCP 首部的「序列号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。
接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
2️⃣第二次握手:
服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(图中为 y ),将此序号填入 TCP 首部的「序列号」字段中,其次把 TCP 首部的「确认号」字段填入 x + 1, 接着把 SYN 和 ACK 标志位置为 1。
最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
3️⃣第三次握手:
客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认号」字段填入 y + 1 ,最后把报文发送给服务端,这次报文可以携带客户端到服务端的数据,之后客户端处于 ESTABLISHED 状态。
服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的。
一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。
可能会有朋友有疑问,为什么第三次握手还要携带 seq = x + 1,只回复 ack = y + 1不行吗?
不行,原因是 TCP 是 "面向字节流的可靠协议",seq(序号)是 TCP 实现 "有序、去重、可靠" 的核心机制,必须在三次握手中完成双方序号的同步 。
具体来说:
- 第一次握手:客户端发送
seq=x,告诉服务器 "我接下来发送的数据,第一个字节的序号是 x";- 第二次握手:服务器回复
seq=y(同步自身序号)+ack=x+1(确认 "已收到 x 及之前的所有数据");- 第三次握手:客户端需要同时完成两个动作:
- 确认服务器的序号 :通过
ack=y+1,告诉服务器 "我已收到你的 seq=y 及之前的所有数据";- 同步自己的下一个序号 :通过
seq=x+1,告诉服务器 "我接下来发送的数据,第一个字节的序号是 x+1"。如果只回
ack=y+1而不带seq=x+1,服务器就不知道客户端后续数据的序号起始值,无法对客户端的数据流进行 "有序、去重" 处理,违背了 TCP 的可靠传输设计。
🔍为什么需要三次握手?而不是两次、四次?
相信大家比较常回答的是:"因为三次握手才能保证双方具有接收和发送的能力。"
但前面也说过了,这个回答比较片面。
接下来,以三个方面分析三次握手的原因:
阻止重复历史连接的初始化(主要原因)
假设有一个场景,那就是客户端先发送了 SYN(seq = x)报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = y)报文(注意!不是重传 SYN,重传的 SYN 的序列号是一样的)。
客户端连续发送多次 SYN建立连接的报文,在网络拥堵情况下:
一个「旧 的SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是(x + 1)。
客户端收到后,发现自己期望收到的确认号应该是 y + 1,而不是 x + 1,于是就会回 RST 报文。
服务端收到 RST 报文后,就会释放连接。
后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的「旧 的SYN 报文」称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接。
而为什么两次握手,就无法阻止历史连接呢?
主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED状态。
假设这次是历史连接,客户端可以判断此次连接为历史连接,那么就会回 RST 报文来断开连接,而服务端在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。这很明显白白浪费了服务端的资源。
因此,要解决这种情况,最好就是在服务端发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手。
所以,TCP 使用三次握手建立连接的最主要原因是防止「历史连接」初始化了连接。
同步双方的初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
接收方可以去除重复的数据;
接收方可以根据数据包的序列号按序接收;
可以标识发送出去的数据包中, 哪些是已经被对方确认收到的(就是通过 ACK 报文中的序列号知道);
所以,「序列号」在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手其实也能够可靠的同步双方的初始化序号,但是很明显图中的第二步和第三步可以优化成一步,所以就成了「三次握手」。
小结
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。
不使用「两次握手」和「四次握手」的原因:
「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号
「四次握手」:三次握手就已经理论上最少可靠连接建立,所以就不需要使用更多的通信次数
📝TCP的四次挥手
TCP断开连接是通过四次挥手的形式
客户端和服务端双方都可以主动断开连接,断开连接后主机中的资源将被释放,四次挥手的过程如下图:

1️⃣第一次挥手
就如图中显示,此刻,客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
2️⃣第二次挥手
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
3️⃣第三次挥手
等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
4️⃣第四次挥手
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态。
服务端收到了 ACK 应答报文后,就进入了 CLOSED 状态,至此服务端已经完成连接的关闭。
客户端在经过 2MSL 一段时间后,自动进入CLOSED 状态,至此客户端也完成连接的关闭。
注意:主动关闭的一方才有 TIME_WAIT 状态
🔍为什么挥手需要四次?
- 关闭连接时,客户端向服务端发送 FIN 时,仅仅代表客户端不再发送数据了,但是它还能接收数据
- 服务端收到客户端发来的 FIN 报文时,先回一个 ACK 应答报文,但服务端此时可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。
❓那为什么要有 TIME_WAIT 状态?
主动关闭连接的一方才会有 TIME_WAIT状态
需要 TIME-WAIT 状态,主要是两个原因:
- 防止历史连接中的数据,被后面相同的连接错误的接收;
- 保证「被动关闭连接」的一方,能被正确的关闭;
防止历史连接中的数据,被错误接收
TCP 连接的唯一标识是 "源 IP + 源端口 + 目的 IP + 目的端口"。如果主动关闭端立即复用相同的四元组建立新连接,可能出现旧连接的残留报文(因网络延迟滞留在链路中)"闯入" 新连接 的情况。
举个例子:
- 旧连接的四元组是
(A:1234, B:80),主动关闭后立即用(A:1234, B:80)建立新连接; - 旧连接中一个延迟的报文(比如
seq=100)此时到达服务器,服务器会误以为这是新连接的报文,导致数据混乱。
而 TIME_WAIT 的**2MSL**等待,能保证:
- 旧连接的所有残留报文,在**
2MSL**内都会从网络中 "消失"(超过 MSL 的报文会被路由器丢弃); - 等**
TIME_WAIT**超时后再复用四元组,新连接就不会被旧连接的残留报文干扰。
保证被动方可以正确的关闭
四次挥手的最后一步,是主动关闭端给被动关闭端发ACK,但是这个ACK可能因为网络延迟 / 丢包而没被被动关闭端收到。
如果主动关闭端不等待**
TIME_WAIT**,直接关闭连接:
被动关闭端会因为没收到
ACK,超时重传FIN报文;但此时主动关闭端已经 "消失",重传的
FIN会被当成 "无效报文",被动关闭端永远无法确认连接已关闭,只能一直卡在LAST-ACK状态。
而**TIME_WAIT** 的**2MSL**等待,能覆盖以下场景:
MSL(Maximum Segment Lifetime)是 "报文在网络中能存活的最长时间"(比如 1 分钟);主动关闭端等待
2MSL,既保证自己发的最后一个ACK有足够时间到达被动关闭端 ,也保证被动关闭端重传的FIN报文(如果有的话)能在超时前被自己收到,并重新发送ACK。
⚠️那TIME_WAIT过多的话,有什么危害
主要的危害包括系统资源和端口资源
第一种:系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
第二种:端口资源,TCP 连接的 "源端口" 是 16 位整数(范围0~65535),其中0~1023是知名端口(固定分配),实际可用的动态端口(客户端临时使用)通常只有约 2 万~6 万个(不同系统配置不同)
如果客户端(主动关闭端)的 TIME_WAIT状态 过多,占满了所有的端口资源,就无法对【目的IP, 目标PORT】一致的服务端建立连接,但是被使用的端口,还可以与另外的服务端建立连接
如果服务端(主动关闭端)的 TIME_WAIT状态过多,并不会导致端口资源受限,因为服务端只监听一个端口,而且由于一个四元组唯一确定一个 TCP 连接,因此理论上服务端可以建立很多连接,但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等
愿每一份坚持都有回响,每一次钻研都有收获。值此2026年伊始,祝大家新的一年里,学有所成,万事顺意❤️❤️❤️