【Java EE】TCP—连接管理

连接管理

连接管理

TCP 标志位:控制报文类型的 6 个比特

TCP 报头中有一个字节的标志位字段,其中 6 个标志位最为常用:

标志位 含义 作用
SYN Synchronize 同步序号,用于建立连接
ACK Acknowledgment 确认序号有效,表示这是一个确认报文
FIN Finish 结束连接,用于断开连接
RST Reset 强制重置连接
PSH Push 通知接收方立即将数据交给应用层
URG Urgent 紧急指针有效(很少使用)

关键 :SYN 和 ACK 本身不携带载荷(即没有应用层数据),它们只用于控制连接的建立与确认。

三次握手:建立连接

为什么需要三次握手?

三次握手主要达成三个目标:

  1. 投石问路:确认通信路径上是否畅通(双方都能发送和接收);
  2. 验证能力:确认双方的发送能力和接收能力都正常;
  3. 协商关键数据:比如初始序号(ISN)、最大报文段长度(MSS)等。

其中初始序号的协商尤其重要------双方各自随机生成一个起始序号,避免历史连接的数据包干扰新连接。

三次握手流程⭐

客户端主动发起连接,服务器被动监听。
服务器 客户端 服务器 客户端 CLOSED LISTEN 收到 SYN,进入 SYN_RCVD 收到 SYN+ACK,进入 ESTABLISHED 收到 ACK,进入 ESTABLISHED 第一次握手:SYN (seq=x) 客户端进入 SYN_SENT 第二次握手:SYN+ACK (seq=y, ack=x+1) 第三次握手:ACK (seq=x+1, ack=y+1)

  • 第一次握手 :客户端发送 SYN 段,序号 seq=x(x 为客户端随机生成的初始序号)。客户端进入 SYN_SENT 状态。
  • 第二次握手 :服务器收到 SYN 后,回复 SYN+ACK 段。其中服务器自己的序号 seq=y(y 为服务器随机生成的初始序号),确认序号 ack=x+1(表示已收到客户端的序号 x,期望下次收到 x+1)。服务器进入 SYN_RCVD 状态。
  • 第三次握手 :客户端收到 SYN+ACK 后,回复 ACK 段,seq=x+1ack=y+1。客户端进入 ESTABLISHED 状态。服务器收到 ACK 后也进入 ESTABLISHED 状态。

注意:SYN 和 SYN+ACK 都不携带数据,但会消耗一个序号;纯 ACK 如果不携带数据,则不消耗序号。

状态 出现位置 含义
CLOSED 连接的最初 / 完全关闭后 没有活动连接,或连接已彻底释放
LISTEN 服务器调用 listen() 服务器正在监听端口,等待客户端发起连接
SYN_SENT 客户端发送第一次握手(SYN)后 客户端已发出连接请求,等待服务器回复 SYN+ACK
SYN_RCVD 服务器收到 SYN 并回复 SYN+ACK 后 半连接状态,不分配大资源。服务器已收到客户端的 SYN,等待客户端的最终 ACK
ESTABLISHED 第三次握手(ACK)到达对方后 连接已成功建立,双方可以传输数据

补充说明

  • CLOSED 既是初始状态,也是 TIME_WAIT 超时后的最终状态。
  • 客户端从 CLOSEDSYN_SENTESTABLISHED
  • 服务器从 LISTENSYN_RCVDESTABLISHED

两次握手与三次握手对比⭐

如果只有两次握手,服务器无法确认客户端是否收到了自己的 SYN+ACK。而且,网络中可能滞留有旧的 SYN 段,两次握手会导致服务器错误地建立连接,浪费资源。三次握手可以让客户端在第三次 ACK 中确认对方的能力,同时通过序号机制区分新旧连接。
服务器 客户端 服务器 客户端 两次握手模型(不可靠) 服务器立即进入 ESTABLISHED! 开始分配资源等数据 客户端收到后也进入 ESTABLISHED 但若客户端根本没收到 SYN+ACK 呢? 服务器已盲目消耗资源 更糟:旧 SYN 迟到 服务器以为是新请求, 立即回复 SYN+ACK 并进入 ESTABLISHED 白白浪费资源 资源已浪费 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) 旧 SYN (seq=old) 延迟到达 收到不请自来的 SYN+ACK,发 RST 拒绝
服务器 客户端 服务器 客户端 三次握手模型(可靠) 只进入 SYN_RCVD(半连接) 不分配大资源 收到 ACK 才进入 ESTABLISHED 确认客户端有能力且需要连接 处理旧 SYN 收到 RST,关闭半连接,无大损失 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (ack=y+1) 旧 SYN 延迟到达 回复 SYN+ACK,保持 SYN_RCVD 半连接 客户端发现不对,发 RST

第x次握手数据丢失的情况分析⭐

在 TCP 三次握手建立连接的过程中,无论哪一次握手报文丢失,TCP 协议栈都会通过超时重传机制来补救。但具体表现和处理细节因丢失的阶段不同而有所差异。

丢失阶段 触发重传的一端 关键机制
第一次握手(SYN) 客户端 客户端 SYN 重传,直到超时或成功

服务器 客户端 服务器 客户端 主动打开,发送 SYN 服务器未收到,无感知 进入 SYN_SENT, 启动重传定时器 收到 SYN, 进入 SYN_RCVD loop [超时重传(默认重试 5~6 次)] 收到 SYN+ACK, 进入 ESTABLISHED 收到 ACK,进入 ESTABLISHED 连接建立成功 ① SYN (seq=x) ❌ 丢失 ② 重传 SYN (seq=x) ③ SYN+ACK (seq=y, ack=x+1) ④ ACK (ack=y+1)

丢失阶段 触发重传的一端 关键机制
第二次握手(SYN+ACK) 客户端 + 服务器 客户端重传 SYN,服务器重传 SYN+ACK,两端独立超时

服务器 客户端 服务器 客户端 收到 SYN,进入 SYN_RCVD 启动 SYN+ACK 重传定时器 未收到 SYN+ACK 启动 SYN 重传定时器 服务器 SYN+ACK 重传定时器超时 客户端 SYN 重传定时器超时 par [两端独立超时重传] 收到重传 SYN,识别为重复 立即回复 SYN+ACK 收到 SYN+ACK,进入 ESTABLISHED 收到 ACK,进入 ESTABLISHED 连接建立成功 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ❌ 丢失 ③ 重传 SYN+ACK ❌ 仍丢失 ④ 重传 SYN (seq=x) ⑤ SYN+ACK (seq=y, ack=x+1) ⑥ ACK (ack=y+1)

丢失阶段 触发重传的一端 关键机制
第三次握手(ACK) 服务器 服务器重传 SYN+ACK,客户端重传 ACK;数据报文也可能直接完成握手

服务器 客户端 服务器 客户端 收到 SYN+ACK,进入 ESTABLISHED 未收到 ACK,仍为 SYN_RCVD 启动 SYN+ACK 重传定时器 收到重复 SYN+ACK, 识别为重复,立即重传 ACK 收到 ACK,进入 ESTABLISHED 丢失 ACK 后立即发送 HTTP 请求等数据 检查 ack=y+1,确认客户端 已收到自己的 SYN 直接从 SYN_RCVD 进入 ESTABLISHED 并处理数据 alt [情况一:无数据发送(纯 ACK 丢失)] [情况二:客户端紧接着发送数据(捎带应答)] 连接建立成功 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (ack=y+1) ❌ 丢失 ④ 超时后重传 SYN+ACK ⑤ ACK (ack=y+1) ⑥ 数据包 (PSH, ACK, seq=x+1, ack=y+1) 内容:GET /index.html

四次挥手:断开连接

四次挥手流程

TCP 连接是全双工的,每个方向的数据传输可以独立关闭。四次挥手就是关闭一个方向上传输的完整过程,需要四个报文段。
被动关闭方(服务器) 主动关闭方(客户端) 被动关闭方(服务器) 主动关闭方(客户端) 初始状态:ESTABLISHED 进入 FIN_WAIT_1 进入 CLOSE_WAIT 收到 ACK,进入 FIN_WAIT_2 应用层读取剩余数据后 调用 close(),发送 FIN 进入 LAST_ACK 进入 TIME_WAIT 收到 ACK,进入 CLOSED 等待 2MSL 后,进入 CLOSED ① FIN (seq=u) ② ACK (ack=u+1) ③ FIN (seq=v, ack=u+1) ④ ACK (ack=v+1)

状态流转说明

为了更好地理解双方在整个挥手过程中的状态变化,下面给出一个状态流转图(以主动关闭方和被动关闭方为两条线)。
主动关闭方
被动关闭方
发送 FIN
收到 ACK
收到 FIN

发送 ACK
等待 2MSL 超时
收到 FIN

发送 ACK
应用调用 close()

发送 FIN
收到 ACK
ESTABLISHED
FIN_WAIT_1
FIN_WAIT_2
TIME_WAIT
CLOSED
CLOSE_WAIT
LAST_ACK

注意:CLOSE_WAIT 状态会持续到应用程序主动调用 close(),如果应用不关闭,连接会长期滞留在此状态,导致资源泄漏。

状态 出现方 含义
FIN_WAIT_1 主动方 已发送 FIN,等待对方的 ACK
FIN_WAIT_2 主动方 已收到 ACK,等待对方发送 FIN
CLOSE_WAIT 被动方 已收到 FIN 并回复 ACK,等待本地应用关闭
LAST_ACK 被动方 已发送 FIN,等待对方的最终 ACK
TIME_WAIT 主动方 已发送最后 ACK,等待 2MSL 确保对方收到
CLOSED 双方 连接已彻底释放

为什么需要 TIME_WAIT?⭐

主动关闭方在发送最后一个 ACK 后,必须等待 2MSL (Maximum Segment Lifetime,报文最大生存时间,典型值 30 秒~2 分钟)才能进入 CLOSED。原因有两点:

原因一:确保最后的 ACK 能到达对方

网络不可靠,最后一个 ACK 可能丢失。如果主动方直接关闭,被动方会因未收到 ACK 而不断重传 FIN。此时主动方的端口可能已被新连接占用,导致新的连接收到一个意外的 FIN,发生错乱。

TIME_WAIT 状态下,主动方可以:

  • 收到重传的 FIN 后,重新发送 ACK;
  • 保证被动方能正常进入 CLOSED

下图展示了最后一个 ACK 丢失时的处理流程:
被动关闭方 主动关闭方 被动关闭方 主动关闭方 进入 TIME_WAIT,启动 2MSL 定时器 未收到 ACK,超时重传 FIN 仍在 TIME_WAIT, 收到重传 FIN 后重新发送 ACK 收到 ACK,进入 CLOSED 重新启动 2MSL 定时器(部分实现) FIN ACK FIN ACK 丢失 ❌ 重传 FIN ACK (ack=v+1)

原因二:防止旧连接的数据包干扰新连接

等待 2MSL 的时间,足以让网络中所有属于本次连接的残留报文(包括重传的 FIN、延迟到达的数据段等)全部消失。这样当同一个端口对(IP + 端口)被新连接复用时,就不会收到上一个连接的幽灵报文,保证新连接的 TCP 序列号空间干净。

为什么四次挥手不能像三次握手那样合并?⭐

在三次握手中,服务器可以把 SYN 和 ACK 合并在一个报文里(SYN+ACK),因为它们都由内核在收到 SYN 时立刻产生,时机完全同步。

但在四次挥手中:

  • 第二次挥手(ACK):由内核 在收到 FIN 时立刻自动回复
  • 第三次挥手(FIN):需要等到应用层调用 close() 才会触发。

这两个动作之间存在用户态的时间延迟(例如应用还需读取剩余数据、清理资源等),因此 ACK 和 FIN 通常是分开发送的,形成四次交互。
主动方 被动方内核 被动方应用 主动方 被动方内核 被动方应用 内核立即回复 ACK 通知应用有 FIN 到达 内核发送 FIN FIN ACK 读取剩余数据(如有) 应用调用 close() FIN ACK

当然,如果应用层恰好在收到 FIN 的那一刻也正要关闭连接(或根本没有数据要处理),内核在收到 FIN 后可以将 ACK 与 FIN 合并 成一个报文段(即 FIN+ACK),这被称为同时关闭优化。但这并非常态,所以普遍情况下依然是四次挥手。
主动方 被动方 主动方 被动方 应用恰好在此时也准备关闭 内核合并 ACK 和 FIN 进入 FIN_WAIT_1 / FIN_WAIT_2 等后续状态 FIN FIN+ACK (seq=y, ack=x+1)

TCP 状态转换图

下面用 Mermaid 状态图展示客户端和服务器在连接建立与断开过程中的状态迁移。
服务器调用 listen
客户端调用 connect
收到 SYN
收到 SYN+ACK
收到 ACK
主动关闭,发送 FIN
被动关闭,收到 FIN
收到 ACK
收到 FIN(同时关闭)
收到 FIN
应用程序调用 close,发送 FIN
收到 ACK
等待 2MSL 超时
收到 ACK
CLOSED
LISTEN
SYN_SENT
SYN_RCVD
ESTABLISHED
FIN_WAIT_1
CLOSE_WAIT
FIN_WAIT_2
CLOSING
TIME_WAIT
LAST_ACK

Socket 编程与 TCP 状态的对应关系⭐

应用操作 TCP 发生事件 状态变化(客户端) 状态变化(服务器)
服务器 new ServerSocket(port) socket(), bind(), listen() - CLOSEDLISTEN
客户端 socket.connect() 发送 SYN CLOSEDSYN_SENT 收到 SYN,回复 SYN+ACK,连接进入半连接队列
服务器 accept() 返回 三次握手完成,连接移到全连接队列 SYN_SENTESTABLISHED(内核自动完成) 连接在 TCP 层已为 ESTABLISHED,现在应用获取到它
客户端 close() 发送 FIN ESTABLISHEDFIN_WAIT_1 收到 ACK → FIN_WAIT_2 收到 FIN,回复 ACK → CLOSE_WAIT
服务器读操作返回 EOF (如 scanner.hasNext()==false 应用层读取到 FIN 指示 (客户端已关闭发送方向) 仍为 CLOSE_WAIT,等待应用关闭
服务器 close() 发送 FIN 收到 FIN,回复 ACK → TIME_WAIT CLOSE_WAITLAST_ACK → 收到 ACK → CLOSED

服务器端的典型流程:
服务器应用 服务器TCP 客户端TCP 客户端应用 服务器应用 服务器TCP 客户端TCP 客户端应用 CLOSED 进入 LISTEN 进入 SYN_SENT 进入 ESTABLISHED 返回已建立的连接 ESTABLISHED 双方数据通信(ESTABLISHED) 进入 FIN_WAIT_1 进入 CLOSE_WAIT 收到 ACK → FIN_WAIT_2 进入 LAST_ACK 进入 TIME_WAIT 收到 ACK → CLOSED 等待 2MSL → CLOSED new ServerSocket(port) / listen() socket.connect() ① SYN ② SYN+ACK ③ ACK accept() close() ① FIN ② ACK 读取到 EOF(scanner.hasNext()==false) close() ③ FIN ④ ACK

相关推荐
少年攻城狮4 小时前
阿里云系列---【申请域名并绑定到主机ip】
linux·服务器·tcp/ip·阿里云·云计算
周末也要写八哥4 小时前
TCP三次握手与四次挥手的过程
java·网络·tcp/ip
汤愈韬4 小时前
hcip-security_防火墙高可靠技术4—双机热备结合NAT
网络·网络协议·网络安全·security
中科三方4 小时前
域名解析修改后,用户仍访问旧IP?原因排查与高效解决指南
网络协议·tcp/ip·php
辣椒思密达4 小时前
大规模数据采集如何稳定使用海外住宅IP?3种实战方法
网络·网络协议·tcp/ip
xhbh6664 小时前
Linux转发完全教程:ip_forward开启、iptables端口映射、双网卡NAT实战
服务器·网络·智能路由器·端口转发·端口映射·映射
Shota Kishi4 小时前
ERPC 平台全面支持 16 种语言 — 以母语使用 Solana RPC 官方网站与 Dashboard
网络·网络协议·rpc
luojiezong4 小时前
场景驱动下的高校全光网络演进:以太彩光技术路线的适配性解析
网络
千匠网络4 小时前
千匠网络制造行业渠道分销B2B解决方案:AI驱动,重构产业分销模式
网络·云原生·架构·制造业·b2b·电商解决方案