Skip to content

02 · TCP 连接管理:三次握手、四次挥手、TIME_WAIT ​

面试第一高频。但这题真正的分水岭不在"背出三次",而在**"为什么不是两次"和"TIME_WAIT 为什么必须等 2MSL"**。

一句话本质 ​

TCP 是双向的可靠通道。建立时要双方各自确认"我能发、你能收",断开时要双方各自确认"我没数据了"——所以各要一轮半。

生活类比:打电话 ​

TCP打电话
三次握手"喂,你能听见吗?" → "能听见,你能听见我吗?" → "能听见"
四次挥手"我说完了" → "好,我知道了" → (对方也说完)"我也说完了" → "好,挂吧"
TIME_WAIT挂电话后等一会再走,万一对方还有话没说完
半连接队列电话响了但还没接起来的那些
SYN Flood有人一直打骚扰电话,让你接线员忙不过来

为什么不能只问一句"能听见吗"? 因为你说"能听见吗"只证明你的话筒好使,不能证明你的听筒好使。必须对方回一句、你再确认一句,双方才都确认了"我发得出、我收得到"。

核心机制 ​

先看全景:建立要 3 个报文段,断开要 4 个。

← 左右滑动看完整图 →客户端服务器时间三次握手:双方各自确认「我发得出、你收得到」SYN, seq=xSYN+ACK, seq=y, ack=x+1ACK, ack=y+1连接建立,双方进入 ESTABLISHED四次挥手:两个方向各自关闭FIN, seq=uACK, ack=u+1… 继续发剩余数据 …FIN, seq=wACK, ack=w+1客户端进入 TIME_WAIT,等 2MSL 才真正关闭
第三次握手不是客套:它让服务器确认「客户端真的收到了我的 SYN」,否则一个迷路的旧 SYN 就能骗走一份连接资源。四次挥手多出来的一次,是因为 ACK 和 FIN 不能合并——服务器可能还有数据没发完。

一、三次握手(建立连接) ​

三次各自确认了什么(这是回答"为什么三次"的核心):

次数谁确认了什么
第 1 次后服务器确认:客户端发送能力正常、自己接收能力正常
第 2 次后客户端确认:自己发送正常、自己接收正常、服务器收发都正常
第 3 次后服务器确认:自己发送正常、客户端接收正常

只有第 3 次完成后,双方才都确认了"四个方向全通"。

二、为什么不是两次(最高频追问) ​

假设只有两次握手(客户端发 SYN,服务器回 SYN+ACK 就认为连接建立):

场景:一个"迷路的旧 SYN"

① 客户端很久以前发了一个 SYN(seq=100),在网络里堵了很久
② 客户端等不到,超时重发了一个新的 SYN(seq=200),完成握手,传完数据,正常关闭
③ 此时,那个迷路的旧 SYN(seq=100)终于到达服务器

两次握手的情况:
   服务器收到旧 SYN > 立刻回 SYN+ACK > 并认为"连接已建立",分配资源、进入 ESTABLISHED
   但客户端根本没想连(它认为这是垃圾包,回一个 RST 或者直接忽略)
   > 服务器白建了一个连接,占着资源干等,直到超时

三次握手的情况:
   服务器回 SYN+ACK 后进入 SYN_RCVD(半开状态),不分配完整资源
   客户端收到这个莫名其妙的 SYN+ACK > 回 RST
   服务器收到 RST > 释放,什么损失都没有

一句话总结:三次握手的第三次,是让服务器"确认客户端真的收到了我的 SYN",从而避免被历史连接骗走资源。

(另一种说法:两次握手无法防止"已失效的连接请求突然又传到服务端",本质是同一件事。)

三、四次挥手(断开连接) ​

TCP 是全双工的,两个方向要各自关闭:

客户端发完 FIN 后进入 FIN_WAIT_1,收到服务器的 ACK 就转 FIN_WAIT_2——此时「客户端 → 服务器」这个方向已经关了,但反方向还开着。服务器那边进入 CLOSE_WAIT,把剩余数据发完,再补一个 FIN;客户端回最后一个 ACK,进入 TIME_WAIT。

注意 FIN_WAIT_2 和 CLOSE_WAIT 是同一时刻、分处两端的两个状态:一个在等对方的 FIN,一个在等自己的程序调 close()。

为什么是四次不是三次:因为服务器收到 FIN 后,可能还有数据没发完。所以 ACK 和 FIN 不能合并,必须分开发。

例外:如果服务器收到 FIN 时确实没数据要发了,ACK 和 FIN 可以合并成一次(延迟确认机制),变成"三次挥手"。这是常见的追问点。

四、TIME_WAIT:为什么必须等 2MSL ​

主动关闭的一方(这里是客户端)在发完最后一个 ACK 后,进入 TIME_WAIT,等 2MSL(MSL = 报文最大生存时间,Linux 上通常 60 秒,所以等 120 秒)才真正关闭。

两个理由(必须都说):

理由 1:保证最后一个 ACK 能到达对方

如果最后那个 ACK 丢了:
   服务器会重发 FIN(因为它没收到 ACK)
   此时客户端如果已经关闭 > 收到 FIN 会回 RST
   服务器收到 RST 会认为"异常终止",而不是"正常关闭"

如果客户端还在 TIME_WAIT:
   收到重发的 FIN > 再回一次 ACK > 服务器正常关闭

理由 2:让旧连接的残留报文在网络中消亡

如果没有 TIME_WAIT,客户端立刻用相同的四元组建新连接:
   旧连接的延迟报文(比如一个迟到的数据段)到达服务器
   服务器以为是新连接的数据 > 数据错乱

等 2MSL,足以让这个方向上所有报文都过期消失
(一来一回的最长时间 = 2 × MSL)

为什么是 2MSL 而不是 1MSL:因为要覆盖"我的 ACK 到对方"+"对方的 FIN 到我"这一个完整往返。

五、CLOSE_WAIT 过多 = 代码 bug ​

CLOSE_WAIT 状态:被动关闭方收到了 FIN,回了 ACK,但自己的程序还没调 close()

大量 CLOSE_WAIT 意味着:你的程序收到了对方的关闭请求,但没有正确关闭 socket(忘了 close()、或者连接池没释放、或者异常路径没走到 finally)。

这不是网络问题,是代码问题。而大量 TIME_WAIT 通常是正常的(主动关闭方多),可以通过 SO_REUSEADDR、调整内核参数缓解。

状态谁的问题怎么办
大量 TIME_WAIT主动关闭方,通常正常调 tcp_tw_reuse、用长连接
大量 CLOSE_WAIT被动关闭方,代码 bug检查 close 调用、连接池泄漏

六、SYN 队列与 SYN Flood ​

服务器维护两个队列:
  半连接队列(SYN queue):收到 SYN,回了 SYN+ACK,等对方 ACK
  全连接队列(accept queue):握手完成,等应用调 accept()

SYN Flood 攻击:
  攻击者发大量 SYN,但**不回最后一个 ACK**
  > 半连接队列被塞满
  > 正常用户的 SYN 被丢弃
  > 服务不可用

防御:
  ① SYN Cookie:不回 SYN+ACK 时不分配资源,把状态编码进 seq 号
  ② 增大队列、缩短超时
  ③ 限流 / 防火墙

为什么这么设计 ​

反事实 1:如果两次握手 ​

服务器会被历史 SYN 骗走资源(上面详述)。更严重的是:服务器无法区分"客户端想连"和"客户端早就不要了",在 SYN Flood 场景下更容易被打垮。

反事实 2:如果没有 TIME_WAIT ​

  1. 最后一个 ACK 丢了 > 服务器一直重发 FIN,客户端已关闭回 RST > 服务器报异常关闭,日志里全是错误。
  2. 旧连接的延迟报文污染新连接 > 数据错乱,且极难排查(偶发、无法复现)。

反事实 3:如果 TCP 是半双工(只能一方说) ​

那断开只要两次挥手就够了。但 TCP 是全双工,两个方向独立关闭,所以需要四次。

面试怎么问 ​

Q:三次握手的过程?为什么不是两次?

先讲过程(SYN / SYN+ACK / ACK,带序号),再讲"四个方向"的确认逻辑,最后用"历史 SYN 骗资源"说明为什么两次不行。 加分:提到"第三次握手可以携带数据"(前两次不行,因为还没建立,带数据容易被利用放大攻击)。

Q:四次挥手的过程?为什么不是三次?

因为全双工,服务器收到 FIN 时可能还有数据要发,所以 ACK 和 FIN 分开。 加分:如果服务器确实没数据了,ACK+FIN 可以合并,实际就是三次。

Q:TIME_WAIT 为什么要等 2MSL?

两个理由:① 保证最后的 ACK 能到达(丢了还能重发);② 让旧连接的残留报文消亡,避免污染新连接。2MSL 覆盖一个完整往返。

Q:服务器出现大量 CLOSE_WAIT 怎么办?

这是代码没关闭连接的信号(不是网络问题)。检查:连接池是否泄漏、异常分支是否漏了 close、是否用了 try-with-resources / defer。

Q:SYN Flood 怎么防?

SYN Cookie(不预分配资源,把状态编进 seq)、增大半连接队列、缩短 SYN 超时、上游限流。

追问链:

三次握手过程?
  > 为什么是三次不是两次?(历史 SYN 骗资源)
    > 第三次能带数据吗?(能,前两次不能)
      > 序号为什么是随机的?(防止历史报文被误认为当前连接 / 安全)
        > 那 ISN 怎么生成的?(时钟 + 四元组哈希,Linux 做法)
          > 断开为什么是四次?(全双工,两个方向独立关)
            > TIME_WAIT 为什么 2MSL?(ACK 可达 + 残留报文消亡)
              > 大量 TIME_WAIT 怎么办?(SO_REUSEADDR / 长连接 / 调参)

和其他知识的连接 ​

自测 3 问 ​

Q1:第三次握手可以携带数据吗?前两次呢?

点开看答案

第三次可以,前两次不行。

  • 第一次(SYN)不能带数据:此时服务器还没分配资源,如果允许带数据,攻击者可以用一个 SYN 塞满数据,放大攻击(SYN Flood 的变种)。
  • 第二次(SYN+ACK)也不能:客户端此时还没确认连接。
  • 第三次(ACK)可以:客户端已经确认了服务器的收发能力,服务器收到这个 ACK 后连接就建立了,数据可以直接跟上。

实际上很多实现会把第一个数据段和第三次握手合并(TCP Fast Open 更进一步,在 SYN 里就带数据,用 Cookie 验证)。

Q2:为什么序号(ISN)要随机,不能从 0 或 1 开始?

点开看答案

两个原因:

  1. 防止历史报文被误接收。如果每次都从 1 开始,一个上一次连接的延迟报文(seq=500)到达时,可能正好落在新连接的接收窗口内,被当成有效数据。
  2. 安全。可预测的 ISN 让攻击者能伪造 TCP 包(TCP 序列号预测攻击),注入数据或劫持连接。

Linux 的 ISN 生成方式大致是:ISN = 时钟(每 4 微秒加 1)+ 四元组哈希。既难预测,又保证同一四元组短时间内不会重复。

Q3:大量 TIME_WAIT 是坏事吗?

点开看答案

不一定是坏事。TIME_WAIT 出现在主动关闭方,说明你的服务在主动关连接——这是正常的(比如 HTTP 短连接、服务端主动踢掉空闲连接)。

问题在于:

  • 每个 TIME_WAIT 占用一个四元组,端口有限(约 6 万个可用端口),大量短连接会导致端口耗尽,无法建新连接。
  • 内存占用(每个 TIME_WAIT 一个小结构)。

处理方式(按推荐顺序):

  1. 用长连接(HTTP keep-alive、连接池)——根治。
  2. net.ipv4.tcp_tw_reuse = 1——允许复用 TIME_WAIT 的端口建新连接(安全,因为序号已经随机化)。
  3. 不要用 tcp_tw_recycle——它在 NAT 环境下会导致连接失败,已被移除。

对比:CLOSE_WAIT 才是真问题,那是代码没关连接。

类比失效处 / 易错点 ​

  • "打电话"类比会让人以为握手是"聊天"。实际每个报文都有严格格式和序号,且可能丢失重传。
  • "三次握手建立连接"不严谨。TCP 没有"连接"这个实体,所谓连接只是双方各自维护的一份状态(四元组 + 序号 + 窗口)。断网时双方状态还在,所以会"假连接"。
  • TIME_WAIT 出现在主动关闭方,不是固定出现在客户端。谁先 close() 谁进 TIME_WAIT。HTTP 里通常是服务端主动关(如果设了 Connection: close),所以服务端 TIME_WAIT 多。
  • 半关闭(half-close):调用 shutdown(SHUT_WR) 只关写方向,还能继续读。这就是四次挥手能存在的基础。
  • RST 不是"正常关闭"。收到 RST 意味着对方不认这个连接(端口没监听、进程崩了、或者被防火墙拦),会立刻释放,不走四次挥手。

✅ 验收清单 ​

  • 能画出三次握手的时序图,标出 seq/ack 的具体变化
  • 能说清"三次各自确认了哪几个方向",并用此推出"为什么两次不行"
  • 能用"历史 SYN 骗资源"完整讲一个两次握手会出事的场景
  • 能画出四次挥手时序图,并解释"为什么 ACK 和 FIN 不能合并"
  • 能说出 TIME_WAIT 等 2MSL 的两个理由,并解释为什么是 2 而不是 1
  • 能区分 TIME_WAIT 和 CLOSE_WAIT 的成因,并说出各自的处理方式
  • 能解释 SYN Flood 的原理和 SYN Cookie 的防御思路
  • 合上书,用自己的话讲 3 分钟,中途不卡壳