面试官,不要再问我三次握手和四次挥手
Category(分类): NET Protocol Status: 优
三次握手和四次挥手是很多公司常见的面试题,也具有一定的区分度,常常被面试官当作热身题。很多小伙伴刚开始回答得不错,但继续追问后就不知道如何展开。
面试中越简单的问题,越可能隐藏着一些基础但容易混淆的细节。理解三次握手和四次挥手,不能只背诵报文顺序,还应当理解序列号、确认号、TCP 状态、半连接队列、SYN 攻击和 TIME-WAIT 等内容。
本文将围绕以下问题进行介绍:
- 三次握手的过程是什么?
- 为什么连接建立需要三次握手,两次是否可以?
- 什么是半连接队列和全连接队列?
- ISN(Initial Sequence Number)是固定的吗?
- 三次握手过程中可以携带数据吗?
- 如果第三次握手丢失,客户端和服务端会如何处理?
- SYN Flood 攻击是什么?
- 四次挥手为什么通常需要四个报文?
- TIME-WAIT 等待 2MSL 的意义是什么?

1. 三次握手
三次握手(Three-way Handshake)是 TCP 建立连接时,通信双方通常进行的三段报文交换。它的主要作用包括:
- 让双方确认彼此的发送能力和接收能力;
- 同步双方的初始序列号(ISN);
- 确认双向通信路径基本可用;
- 协商 MSS、窗口扩大、SACK 等 TCP 选项;
- 避免旧的、延迟到达的连接请求误建立新连接。
“三次”描述的是正常情况下的三段握手报文。如果发生丢包、重传或超时,实际网络中看到的报文数量可能超过三个。
开始时,主动连接的一方通常处于 CLOSED 状态,被动监听的一方处于 LISTEN 状态。客户端执行 connect() 时触发主动打开(active open),服务端执行 listen() 并等待连接时属于被动打开(passive open)。
1.1 三次握手过程
假设客户端的初始序列号为 x,服务端的初始序列号为 y。
第一次握手
客户端向服务端发送 SYN 报文:
客户端 -> 服务端
SYN=1
seq=x
客户端进入 SYN-SENT 状态,表示客户端请求建立连接,并告诉服务端:“我的初始序列号是 x。”
SYN 控制位本身会占用一个序列号,因此服务端后续确认客户端 SYN 时使用 ack=x+1。
第二次握手
服务端收到客户端的 SYN 后,发送 SYN-ACK 报文:
服务端 -> 客户端
SYN=1, ACK=1
seq=y
ack=x+1
服务端进入 SYN-RECEIVED 状态。这个报文同时完成两件事:
- 使用
ack=x+1确认已经收到客户端的 SYN; - 使用
seq=y告诉客户端服务端自己的初始序列号。
第三次握手
客户端收到服务端的 SYN-ACK 后,发送 ACK 报文:
客户端 -> 服务端
ACK=1
seq=x+1
ack=y+1
客户端可以进入 ESTABLISHED 状态。服务端收到这个 ACK 后,也进入 ESTABLISHED 状态,双方可以开始传输应用数据。
第三个 ACK 不携带应用数据时不会额外消耗序列号;如果携带数据,数据部分会从 seq=x+1 开始编号。

1.2 为什么需要三次握手,两次不行吗?
三次握手并不是为了简单地“多发一个包”,而是为了让双方都确认双向通信状态和序列号同步结果。
- 第一次握手后,服务端知道客户端具备发送能力,并且服务端能够接收客户端的 SYN;
- 第二次握手后,客户端知道服务端具备发送和接收能力,同时客户端也知道自己的 SYN 已经到达服务端;
- 第三次握手后,服务端才知道客户端已经收到自己的 SYN-ACK,双方才完成双向确认。
如果只使用两次握手,服务端无法确认客户端是否收到了自己的 SYN-ACK。假设客户端发送了一个 SYN,但该报文在网络中延迟,客户端后来因超时重试并建立了连接;当之前延迟的旧 SYN 到达服务端时,服务端可能把它误认为一次新的连接请求。如果没有客户端最后的 ACK,服务端无法确认这是不是一个仍然有效的连接。
第三次握手的 ACK 还可以让服务端确认客户端确实收到了自己的初始序列号,从而降低旧连接请求误建立的风险。
1.3 什么是半连接队列?
服务端收到客户端的 SYN 并发送 SYN-ACK 后,连接通常处于 SYN-RECEIVED 状态。此时三次握手尚未完成,这类连接请求通常被称为半连接,相关等待结构常被称为半连接队列或 SYN 队列。
当服务端收到第三次握手的 ACK 后,连接才算完成建立,并进入等待应用通过 accept() 取走的队列,通常称为全连接队列或 accept 队列。
需要注意,半连接队列和全连接队列的具体实现由操作系统决定,Linux 等系统还会受到 listen(backlog)、内核参数、SYN Cookies 等机制影响。队列满时可能出现丢包、连接被拒绝或客户端重试等现象。
SYN-ACK 的重传
服务端发送 SYN-ACK 后,如果没有收到客户端的第三次 ACK,通常会根据 TCP 重传计时器重传 SYN-ACK。等待时间一般会按照实现规则逐步增加,常见表现类似指数退避:
1s、2s、4s、8s……
不同操作系统的初始 RTO、最大重传次数和超时策略并不完全相同。超过实现规定的重试上限后,连接状态和相关资源会被清理。
1.4 ISN(Initial Sequence Number)是固定的吗?
不是。每一端建立连接时都会选择自己的初始序列号,ISN 通常会随连接和时间变化,并且应当具有足够的不可预测性。
早期 TCP 规范曾使用类似时钟递增的方式描述 ISN,例如每隔一段时间增加计数器。但现代 TCP 实现通常采用伪随机或加密安全的生成方式,不能简单认为所有系统都“每 4ms 加 1”。
ISN 的作用包括:
- 让双方知道后续数据的序列号起点;
- 识别和排序 TCP 数据;
- 减少旧连接延迟报文段对新连接的影响;
- 增加伪造有效 TCP 报文的难度。
TCP 序列号是 32 位,会循环使用,因此 TCP 还需要结合确认号、窗口和时间戳等机制判断报文是否属于当前有效的数据范围。
1.5 三次握手过程中可以携带数据吗?
在普通 TCP 建连中,应用数据通常在连接进入 ESTABLISHED 后发送。传统教材常把 SYN 报文描述为不携带数据,但现代 TCP 存在例外:TCP Fast Open(TFO)允许在 SYN 中携带早期数据,前提是双方支持并完成相应的 TFO 机制。
因此更准确的说法是:
- 普通 TCP 握手通常不在前两个 SYN/SYN-ACK 报文中携带应用数据;
- 第三个 ACK 可以携带数据;
- 启用 TCP Fast Open 时,SYN 或 SYN-ACK 也可能携带数据;
- 是否接受握手中的数据由 TCP 实现、内核和应用协议共同决定。
不能简单地把“握手中的数据”当成普通已建立连接后的数据处理,应用还需要考虑重放和幂等性等安全问题。
1.6 如果第三次握手丢失,会发生什么?
假设客户端发送的第三个 ACK 丢失:
- 客户端通常已经进入
ESTABLISHED,可能开始发送数据; - 服务端仍处于
SYN-RECEIVED,没有收到确认; - 如果客户端的数据到达服务端,服务端通常可以根据实现处理这些报文,或者重新发送 SYN-ACK;
- 客户端收到服务端重传的 SYN-ACK 后,通常会再次发送 ACK;
- 如果服务端始终收不到 ACK,超过重传次数后会清理半连接;
- 如果客户端一直没有收到服务端的响应,也会根据自身的重传和超时机制终止连接。
具体行为会受到操作系统实现、TCP 选项和防火墙策略影响,但核心是:第三次 ACK 用于让服务端确认客户端已经收到第二次握手。
1.7 SYN Flood 攻击是什么?
SYN Flood 是一种典型的 DoS/DDoS 攻击。攻击者在短时间内发送大量 SYN,常常伪造源地址,使服务端发送的 SYN-ACK 无法得到正常的第三次 ACK。
服务端可能需要为大量未完成的连接维护状态、定时器和队列空间,导致半连接队列、连接跟踪表或 CPU 资源被耗尽,正常用户的连接请求因此可能被丢弃或延迟。
服务端并不是一定要在第二次握手时分配完整连接资源。SYN Cookies 等技术可以让服务端把部分状态编码到返回的序列号中,在收到第三次 ACK 后再恢复连接信息,从而降低半连接队列压力。
检测方法示例:
ss -ant state syn-recv
某些系统也可以使用:
netstat -antp | grep SYN_RECV
如果短时间内出现大量 SYN-RECV,且来源地址分布异常,可能存在 SYN Flood,但仅凭连接状态数量不能直接断定攻击,还需要结合流量、日志和系统指标分析。
常见防护措施包括:
- 启用 SYN Cookies;
- 合理增大 backlog 和半连接队列;
- 调整重传和超时参数;
- 使用防火墙、负载均衡器或 SYN Proxy;
- 限制新建连接速率;
- 使用上游 DDoS 清洗或云防护服务。
2. 四次挥手
建立 TCP 连接通常需要三次握手,而正常终止一个连接通常需要四次挥手(Four-way Termination)。这与 TCP 的半关闭(half-close)特性有关:一端关闭自己的发送方向后,仍然可以继续接收另一端发送的数据。
四次挥手描述的是典型的主动关闭过程,客户端或服务端都可以主动发起。通常情况下需要四个报文,但如果确认和 FIN 可以合并,或者双方同时关闭,实际报文数量和状态变化可能不同。
假设客户端先发起关闭,双方开始时都处于 ESTABLISHED 状态,过程如下。
2.1 四次挥手过程
假设客户端当前发送序列号为 u,服务端当前发送序列号为 v。
第一次挥手
客户端发送 FIN 报文:
客户端 -> 服务端
FIN=1
seq=u
客户端停止发送应用数据,进入 FIN-WAIT-1 状态,等待服务端确认。FIN 控制位会占用一个序列号。
第二次挥手
服务端收到客户端的 FIN 后,先发送 ACK:
服务端 -> 客户端
ACK=1
seq=v
ack=u+1
这里 ack=u+1 是确认号,表示客户端的 FIN 已经收到;seq=v 是服务端自己的序列号,不能把确认号称为 ACK 报文的序列号。
服务端进入 CLOSE-WAIT 状态。客户端收到这个 ACK 后进入 FIN-WAIT-2,此时客户端到服务端的发送方向已经关闭,但客户端仍可接收服务端数据。
第三次挥手
服务端处理完剩余数据、准备关闭自己的发送方向后,发送 FIN-ACK:
服务端 -> 客户端
FIN=1, ACK=1
seq=w
ack=u+1
其中 w 是服务端当前下一个可用的发送序列号,它可能已经因为服务端之前发送的数据而大于 v。服务端进入 LAST-ACK 状态,等待客户端确认。
第四次挥手
客户端收到服务端的 FIN 后,发送最后的 ACK:
客户端 -> 服务端
ACK=1
seq=u+1
ack=w+1
客户端进入 TIME-WAIT 状态。服务端收到这个 ACK 后进入 CLOSED 状态;客户端等待 2MSL 后才进入 CLOSED。

收到 FIN 只表示对方不会再从该方向发送数据,并不代表连接的两个方向同时消失。TCP 允许半关闭,因此服务端在 CLOSE-WAIT 阶段仍可以继续向客户端发送剩余数据。
主动关闭的一方通常进入 TIME-WAIT,但不意味着一定是客户端。服务端如果主动关闭,也可能进入 TIME-WAIT。
在 Socket 编程中,调用 close() 通常会关闭连接并触发 FIN;如果只想关闭一个方向,可以使用 shutdown()。异常终止连接则可能发送 RST,而不是完成正常的四次挥手。
2.2 挥手为什么通常需要四次?
建立连接时,服务端可以把 SYN 和 ACK 放在同一个报文中,因为第二次握手同时完成“同步服务端序列号”和“确认客户端 SYN”两个动作。
关闭连接时,服务端收到客户端 FIN 后,可能还有数据没有发送完,因此通常只能先发送 ACK:
“我收到你不再发送数据的通知了,但我还没有准备好关闭我的发送方向。”
等服务端剩余数据发送完毕后,再单独发送 FIN。于是典型过程是:
客户端 FIN -> 服务端 ACK -> 服务端 FIN -> 客户端 ACK
如果服务端收到客户端 FIN 后已经没有数据要发送,服务端的 ACK 和 FIN 可能合并在一个报文中,实际可能只看到三个报文。双方同时关闭时,也可能进入同时关闭流程。
2.3 TIME-WAIT 和 2MSL
TIME-WAIT 也称为 2MSL 等待状态。MSL(Maximum Segment Lifetime)表示 TCP 实现规定的报文段在网络中允许存在的最长时间。MSL 的具体取值由实现决定,不能简单等同于 IP TTL 的秒数;IP TTL 主要是跳数限制。
主动关闭的一方发送最后一个 ACK 后进入 TIME-WAIT,通常需要等待 2MSL,主要有两个原因。
原因一:确保对方能够完成关闭
如果客户端发送的最后一个 ACK 丢失,处于 LAST-ACK 的服务端会超时重传 FIN。处于 TIME-WAIT 的客户端仍然能够收到这个重复 FIN,并再次发送 ACK,然后重新启动 TIME-WAIT 计时器。
客户端不会主动无条件重发最后 ACK,只有在收到服务端重传的 FIN 时才会重新确认。
等待 2MSL 可以理解为给“最后 ACK 到达服务端”和“服务端重传 FIN 到达客户端”预留足够时间。
原因二:让旧连接报文段从网络中消失
TCP 连接由四元组标识:
源 IP、源端口、目的 IP、目的端口
如果连接刚关闭就使用相同四元组建立新连接,旧连接中延迟到达的报文可能被新连接误认为有效数据。等待 2MSL,可以让旧连接在网络中的报文段有机会过期,降低新旧连接报文混淆的风险。
传统语义下,同一个四元组在 TIME-WAIT 期间通常不能被安全地立即复用。现代操作系统可能根据时间戳、序列号、端口复用选项和具体协议实现允许部分复用,但不能把 SO_REUSEADDR 理解为无条件绕过 TIME-WAIT。
2.4 为什么 TIME-WAIT 不是发送完 ACK 后立即关闭?
理论上,四个报文都已经发送,但最后一个 ACK 可能丢失。若客户端立即进入 CLOSED:
- 服务端无法确认自己的 FIN 已被客户端收到;
- 服务端会重传 FIN;
- 客户端已经不再保留连接状态,可能无法再次返回 ACK;
- 服务端可能长时间停留在
LAST-ACK。
此外,网络中还可能存在属于旧连接的延迟报文。TIME-WAIT 可以同时为重传最后 ACK 和清理旧报文提供时间窗口。
3. 常见状态变化
TCP 状态的典型变化如下:
客户端主动打开:
CLOSED -> SYN-SENT -> ESTABLISHED
服务端被动打开:
LISTEN -> SYN-RECEIVED -> ESTABLISHED
客户端主动关闭:
ESTABLISHED -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT -> CLOSED
服务端被动关闭:
ESTABLISHED -> CLOSE-WAIT -> LAST-ACK -> CLOSED
如果服务端先主动关闭,则两端的主动关闭和被动关闭状态会相应交换。RST 异常关闭、同时关闭、连接重置和超时也会产生不同的状态转换。

4. 面试时可以这样总结
TCP 三次握手的过程是:
客户端 SYN(seq=x)
服务端 SYN(seq=y) + ACK(ack=x+1)
客户端 ACK(ack=y+1)
它用于同步双方初始序列号,并让双方确认双向通信路径和握手结果。第三次 ACK 丢失时,客户端和服务端可能暂时处于不同状态,服务端会重传 SYN-ACK,客户端收到后再次确认。
TCP 四次挥手的典型过程是:
客户端 FIN -> 服务端 ACK -> 服务端 FIN -> 客户端 ACK
之所以通常需要四个报文,是因为收到 FIN 的一方可能还有数据要发送,ACK 和 FIN 不一定能立即合并。主动关闭的一方通常进入 TIME-WAIT,并等待 2MSL,以便处理最后 ACK 的重传和网络中的旧报文。
总结
三次握手和四次挥手不能只背诵“1、2、3、4”四个步骤,还需要理解:
- SYN 和 FIN 都会占用一个序列号;
- ACK 字段确认的是对方下一个期望收到的序列号;
- 三次握手用于同步序列号并确认双向连接;
- TCP Fast Open 是握手携带早期数据的特殊机制;
- 半连接队列与全连接队列由操作系统实现;
- SYN Flood 会消耗服务端半连接相关资源;
- FIN 只关闭一个方向,TCP 支持半关闭;
- TIME-WAIT 通常持续 2MSL,用于处理 FIN 重传和旧报文;
- 主动关闭方不一定是客户端,服务端同样可能进入 TIME-WAIT。
参考:《TCP/IP 详解 卷 1:协议》
参考规范:
作者:夏雪冬日
发布于 2019-10-13 14:33