技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTNET

面试官,不要再问我三次握手和四次挥手

所属馆藏
NET Protocol
文件格式
Markdown
原始路径
NET Protocol/37-面试官,不要再问我三次握手和四次挥手
本文目录5 个章节
  1. 1. 三次握手
  2. 2. 四次挥手
  3. 3. 常见状态变化
  4. 4. 面试时可以这样总结
  5. 总结

面试官,不要再问我三次握手和四次挥手

Category(分类): NET Protocol Status: 优

原文参考一

原文参考二

三次握手和四次挥手是很多公司常见的面试题,也具有一定的区分度,常常被面试官当作热身题。很多小伙伴刚开始回答得不错,但继续追问后就不知道如何展开。

面试中越简单的问题,越可能隐藏着一些基础但容易混淆的细节。理解三次握手和四次挥手,不能只背诵报文顺序,还应当理解序列号、确认号、TCP 状态、半连接队列、SYN 攻击和 TIME-WAIT 等内容。

本文将围绕以下问题进行介绍:

  1. 三次握手的过程是什么?
  2. 为什么连接建立需要三次握手,两次是否可以?
  3. 什么是半连接队列和全连接队列?
  4. ISN(Initial Sequence Number)是固定的吗?
  5. 三次握手过程中可以携带数据吗?
  6. 如果第三次握手丢失,客户端和服务端会如何处理?
  7. SYN Flood 攻击是什么?
  8. 四次挥手为什么通常需要四个报文?
  9. TIME-WAIT 等待 2MSL 的意义是什么?

TCP 连接建立与释放概览图

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 开始编号。

TCP 三次握手报文顺序图

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

TCP 四次挥手报文顺序图

收到 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

  1. 服务端无法确认自己的 FIN 已被客户端收到;
  2. 服务端会重传 FIN;
  3. 客户端已经不再保留连接状态,可能无法再次返回 ACK;
  4. 服务端可能长时间停留在 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 异常关闭、同时关闭、连接重置和超时也会产生不同的状态转换。

TCP 状态转换图

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

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS