TCP 解析:头部格式、三次握手与四次挥手

目录

⏮回顾

㊙️TCP与UDP的头部格式

TCP的头部格式

序列号:

确认号:

[控制位(也就是图中的Code bites):](#控制位(也就是图中的Code bites):)

窗口大小:

UDP的头部格式

源端口、目的端口:

UDP长度:

校验和:

📝TCP的三次握手

1️⃣第一次握手:

2️⃣第二次握手:

3️⃣第三次握手:

🔍为什么需要三次握手?而不是两次、四次?

阻止重复历史连接的初始化(主要原因)

同步双方的初始序列号

小结

📝TCP的四次挥手

1️⃣第一次挥手

2️⃣第二次挥手

3️⃣第三次挥手

4️⃣第四次挥手

🔍为什么挥手需要四次?

[❓那为什么要有 TIME_WAIT 状态?](#❓那为什么要有 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 及之前的所有数据");
  • 第三次握手:客户端需要同时完成两个动作:
    1. 确认服务器的序号 :通过 ack=y+1,告诉服务器 "我已收到你的 seq=y 及之前的所有数据";
    2. 同步自己的下一个序号 :通过 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年伊始,祝大家新的一年里,学有所成,万事顺意❤️❤️❤️

相关推荐
春蕾夏荷_7282977252 小时前
Sockets-2.3.9.9 UDP使用实例
c++·udp
7ACE4 小时前
Wireshark TS | 超时重传时间不翻倍
网络协议·tcp/ip·wireshark
微爱帮监所写信寄信5 小时前
微爱帮监狱寄信写信工具照片高清处理技术架构
开发语言·人工智能·网络协议·微信·php
li星野6 小时前
TCP 套接字+TLS+HTTP 基本认证
网络协议·tcp/ip·http
爱学java的ptt9 小时前
TCP详解
网络·网络协议·tcp/ip
阿拉伯柠檬9 小时前
传输层协议TCP(一)
linux·网络·网络协议·tcp/ip·面试
wanzhong233310 小时前
开发日记2-创建http文件测试http接口
网络·网络协议·http
YJlio11 小时前
PsPing 学习笔记(14.2):TCP Ping——端口连通性与服务在线性秒级体检
笔记·学习·tcp/ip
better_liang21 小时前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题