Skip to content

TCP 三次握手与四次挥手

握手挥手完整时序、打电话通俗比喻、2MSL 深度推导、TIME_WAIT 与 CLOSE_WAIT 生产故障排查。

Updated View as Markdown
For humans

TCP 三次握手与四次挥手

校招考察频度:★ 高频(核心必背·高频生死线) 一句话速记:三次握手确认双方“收发双向通畅”;四次挥手因全双工需双向独立断开;TIME_WAIT 等 2MSL 防丢 ACK 灭旧包,CLOSE_WAIT 堆积必定是业务代码漏调 close。

TCP 连接的建立与断开是校招面试中考查频率最高的问题,没有之一。考官不仅看你能不能默写时序图,更看重你对“为什么必须是三次/四次”的逻辑自洽性,以及面对线上连接堆积故障时的排障思路。


一、三次握手:双方确认收发能力(打电话通俗比喻)

TCP 是面向连接的、可靠的、基于字节流的传输层协议。三次握手的核心本质是:在不可靠的信道上,用最少的成本建立起互信,并同步双方的初始序列号(ISN, Initial Sequence Number)。

TCP 三次握手:同步双方初始序列号

打电话通俗演练法(面试极简脱稿表达)

第 1 次握手(客户端发 SYN):
  “喂,你能听到我说话吗?”
  -> 服务端收到后确认:客户端的发送正常,服务端的接收正常。

第 2 次握手(服务端发 SYN+ACK):
  “听到了!我也在说话,你能听到我说话吗?”
  -> 客户端收到后确认:服务端的发送和接收都正常,客户端自己的发送和接收也都正常!(客户端心里完全有底了)

第 3 次握手(客户端发 ACK):
  “听到了!那咱们开始聊正事吧!”
  -> 服务端收到后确认:客户端的接收也正常!(服务端心里也完全有底了,连接建立)

为什么不能是“两次握手”?

  • 原因 1:防止失效的历史连接请求突然传到服务端产生错误(RFC 规范核心理由)。
    假设客户端发了一个 SYN 报文在某个网络节点滞留了,客户端超时放弃并重开连接;等旧的 SYN 慢悠悠到达服务端时,若只有两次握手,服务端一发 ACK 就单方面认为连接建立,一直在干等客户端发数据,导致服务端资源白白浪费。
  • 原因 2:确认双方的双向收发能力。两次握手只能证明客户端能发、服务端能收,但服务端根本不知道自己的发信能不能被客户端收到。

二、四次挥手:全双工独立断开

TCP 是全双工通信(Full-Duplex):发送端和接收端都可以同时发送和接收数据。两个方向的数据流是完全独立的,因此每个方向都需要单独关闭(主动方说我不发了,被动方说好;被动方处理完说我也不发了,主动方说好)。

四次挥手:两个方向独立关闭

为什么挥手需要四次,而握手只有三次?

  • 建立连接时:服务端收到客户端的 SYN,此时服务端的响应和连接建立意图是同步的,所以可以把 SYN + ACK 合并成一个数据包一起发过去(四次变三次)。
  • 关闭连接时:当主动方发送 FIN 时,仅仅代表主动方不会再发送新的应用数据了;而被动方收到 FIN 时,可能还有数据正缓冲在发送队列里没有传完,所以被动方必须先立即回一个 ACK 告诉对方我收到你的关闭意向,等自己把所有剩下的数据都发完了,才单独再发一个 FIN。因此第二步和第三步不能合并,必须走四次。

三、灵魂考点:TIME_WAIT 状态与 2MSL

主动关闭方发出最后一个 ACK 后,必须在 TIME_WAIT 状态停留 2MSL(Maximum Segment Lifetime,报文最大生存时间):

为什么必须是 2MSL?(两大核心原因,大厂必答)

  1. 保证第四次挥手的 ACK 能可靠到达被动方:
    如果主动方发完最后一个 ACK 就直接 CLOSED,万一这个 ACK 在网络中丢了,被动方就会超时重传第三次的 FIN。如果此时主动方已经没了,就会直接回一个 RST 报错,导致被动方无法优雅地正常关闭(报错 Connection reset by peer)。主动方等 2MSL(来回各一个 MSL),一旦被动方重传 FIN,主动方还能再次补发 ACK。
  2. 防止“已失效的历史报文”混淆新连接:
    经过 2MSL 的时间,本连接产生的所有网络报文都一定已经在网络中老化消逝(TTL 耗尽丢弃),确保后面复用相同四元组(源IP、源端口、目的IP、目的端口)的新连接不会收到上一个旧连接的残留脏数据。

四、生产事故与高频排查手册

故障现象发生在哪一侧根本原因生产排查与排障应对方案
大量 TIME_WAIT 堆积主动关闭方(通常是高并发短连接客户端,或作为反向代理的 Nginx)系统高并发下频繁主动断开短连接,每个连接都要等 2MSL(Linux 默认 60s),耗尽本地可用端口(Local Port Exhaustion)。1. 开启连接复用:HTTP 开启 keep-alive 长连接,数据库用连接池;
2. 系统内核调优:打开 net.ipv4.tcp_tw_reuse = 1(复用处于 TIME_WAIT 的套接字);
3. 扩大可用端口范围 ip_local_port_range。
大量 CLOSE_WAIT 堆积被动关闭方(通常是业务应用服务端)对端已经发了 FIN 想要关闭,但业务代码收到 EOF 后一直没有调用 socket.close(),导致连接一直挂在被动等待态。纯粹是代码 Bug!1. 排查代码:如 HTTP Client 忘记关闭 response.Body、数据库连接未归还连接池、线程池被打满导致无法处理关闭事件;
2. 依靠 netstat -antp | grep CLOSE_WAIT 定位具体的进程 PID 和代码调用堆栈。
SYN Flood 洪水攻击服务端攻击者伪造海量虚假 IP 发送 SYN,服务端分配资源放入**半连接队列(SYN 队列)**并回 SYN+ACK,由于 IP 不存在收不到第三次 ACK,导致半连接队列被瞬间撑爆。1. 开启 tcp_syncookies = 1:半连接队列满时不分配上下文,把信息编码在序列号中,收到第三次 ACK 再解码验证;
2. 调大 net.ipv4.tcp_max_syn_backlog;
3. 防火墙配置防御清洗。

五、面试追问

  1. 三次握手中,第三次 ACK 能带数据吗?
    能!第一次 SYN 和第二次 SYN+ACK 绝对不能带数据(否则容易遭到泛洪攻击),但第三次握手时客户端已经处于 ESTABLISHED 状态,完全可以附带要发送的 HTTP 请求数据。
  2. 如果已经建立了连接,客户端突然宕机了怎么办?
    TCP 自带 保活机制(Keepalive Timer)。服务端若长时间(默认通常 2 小时)没有收到客户端数据,就会启动保活定时器,每隔 75 秒发送一个探测报文,连续 9 次无响应则判定客户端死亡并强制关闭连接。生产中应用层通常会自行设计心跳包(如每 30 秒 ping-pong)提早发现断连。
Navigation

Type to search…

↑↓ navigate↵ selectEsc close