文章目录TCP 三次握手彻底解析SYN、ACK、SEQ、确认号与状态迁移前言一、先区分四个容易混淆的概念二、SEQ 序号到底表示什么三、ack 确认号到底表示什么四、ACK 标志位与确认号字段的关系五、为什么 SYN 会占用一个序号六、TCP 三次握手完整过程七、第一次握手客户端发送 SYN1. 报文方向2. 标志位3. 序号4. 状态变化5. 第一次握手表达的含义八、第二次握手服务器发送 SYN 和 ACK1. 报文方向2. 标志位3. 服务器序号4. 确认号5. 状态变化6. 第二次握手表达的含义九、第三次握手客户端发送最终 ACK1. 报文方向2. 标志位3. 客户端序号4. 确认号5. 状态变化十、三次握手报文汇总十一、代入具体数值理解第一次握手第二次握手第三次握手十二、为什么必须进行三次握手1. TCP 需要同步两个方向的初始序号2. 两次握手无法确认服务器的 SYN 是否到达客户端3. 三次握手可以降低旧连接报文造成的混乱十三、三次握手和 SYN Flood 的关系十四、第三次握手能否携带数据十五、普通 ACK 为什么不消耗序号十六、SEQ 和 ack 的通用计算方法只有 SYN只有 10 字节数据10 字节数据加 FIN十七、客户端和服务器分别维护独立序号空间客户端发送方向服务器发送方向十八、三次握手中的状态迁移客户端状态服务器状态十九、常见错误逐项纠正错误一第一次握手是 SYN1、ACK1错误二第一次握手没有确认号字段错误三第二次握手的 SEQ 是 Y1错误四第三次握手仍然设置 SYN1错误五第三次握手的 SEQ 是 Y错误六确认号永远等于对方 SEQ1错误七SYN 相当于一个虚构的数据字节错误八两次握手必然导致大量 SYN Flood 僵尸连接二十、最终结论TCP 三次握手彻底解析SYN、ACK、SEQ、确认号与状态迁移前言TCP 是一种面向连接、可靠、基于字节流的传输层协议。在客户端和服务器正式传输应用数据之前双方需要通过三次报文交换完成连接建立。这个过程通常被称为TCP 三次握手Three-Way Handshake。很多资料能够正确画出三次握手的基本结构客户端 服务器 | | | -------- SYN -------------- | | ------ SYN ACK ---------- | | -------- ACK -------------- | | |但一旦涉及以下细节就很容易出现混淆SYN和ACK到底是标志位还是字段为什么第二次握手的确认号是X 1为什么第三次握手的序号是X 1为什么不能只握手两次三次握手与 SYN Flood 有什么关系第三次握手能不能携带应用数据本文将使用客户端初始序号X和服务器初始序号Y逐字段拆解整个过程。一、先区分四个容易混淆的概念分析三次握手之前必须先区分SYN标志位ACK标志位SEQ序号字段ACK Number确认号字段为了避免把ACK标志位和确认号字段混在一起本文采用以下写法SYN 表示 SYN 标志位 ACK 表示 ACK 标志位 SEQ 表示序号字段 ack 表示确认号字段也就是说ACK 1表示 TCP 首部中的 ACK 标志位被置为 1。而ack X 1表示 TCP 首部中的确认号字段值为X 1。这两个概念不能混为一谈。二、SEQ 序号到底表示什么TCP 不直接给每个报文编号而是给整个连接中的每一个数据字节编号。假设某个 TCP 报文段携带 100 字节数据并且SEQ 1000那么这段数据占用的序号范围就是1000 1099发送端下一次发送新数据时序号通常从1100开始。因此更准确地说SEQ表示该报文段中第一个数据字节在 TCP 字节流中的序号。对于不携带数据的纯 ACK 报文SEQ通常表示发送端当前下一个准备使用的序号。对于携带 SYN 的报文SEQ字段存放本端的初始序号 ISN。SYN 本身会占用一个序号因此第一个实际数据字节通常从ISN 1开始。RFC 9293 给出的基本三次握手示例同样展示了 SYN 占用一个序号的行为。三、ack 确认号到底表示什么确认号不是简单地表示我收到了哪个序号它真正表达的是我已经按序收到了该确认号之前的所有字节下一次希望收到的序号是 ack。例如服务器发送ack 1001表示序号 1000 及之前需要确认的内容已经收到 下一次期望客户端从序号 1001 开始发送。因此TCP 的 ACK 是一种累计确认。对于按序到达的普通数据段可以近似理解为ack 对方的 SEQ 数据长度但是更加准确的表达应该是ack 接收端下一次期望收到的序号这两种表达在数据按序到达时结果通常相同但后者才是 TCP 确认号的本质定义。四、ACK 标志位与确认号字段的关系TCP 首部中始终存在确认号字段。但是只有当 ACK 标志位为 1 时确认号字段才具有协议意义。因此第一次握手中并不是“确认号字段不存在”而是ACK 0所以确认号字段不生效接收方应当忽略它。某些抓包工具可能仍然显示该字段的数值但这个数值不能被解释为有效确认号。五、为什么 SYN 会占用一个序号普通 TCP 数据每发送一个字节就会消耗一个序号。例如SEQ 100 数据长度 10 字节那么序号范围为100 109下一个序号是110SYN 虽然通常不属于应用层数据但它也会占用一个 TCP 序号。因此SYN 的 SEQ X对端确认时就必须发送ack X 1FIN 也具有相同特性SYN 消耗一个序号 FIN 消耗一个序号但普通纯 ACK 报文不会额外消耗序号。RFC 对 TCP 序号空间的定义明确规定SYN 和 FIN 都占据序号空间。六、TCP 三次握手完整过程下面分析最常见的连接建立方式客户端执行主动打开服务器执行被动打开SYN 报文不携带应用数据不考虑同时打开等特殊情况假设客户端初始序号X 服务器初始序号Y完整过程如下客户端 服务器 CLOSED LISTEN | | | SYN1, ACK0 | | SEQX | | -------------------------------------------------- | | | SYN-SENT SYN-RCVD | | | SYN1, ACK1 | | SEQY, ackX1 | | -------------------------------------------------- | | | | SYN0, ACK1 | | SEQX1, ackY1 | | -------------------------------------------------- | | | ESTABLISHED ESTABLISHED七、第一次握手客户端发送 SYN1. 报文方向客户端 → 服务器客户端主动发起连接。在 Socket 编程中这通常对应客户端调用connect(sockfd,...);之后客户端 TCP 协议栈开始发送连接请求。2. 标志位SYN 1 ACK 0SYN 1表示该报文用于同步初始序号。第一次握手时客户端还没有收到服务器的初始序号因此没有可确认的服务器序号ACK 0需要注意准确说法是ACK 标志位为 0确认号字段无效。而不是TCP 首部中不存在确认号字段。3. 序号SEQ X这里的X是客户端生成的初始序号也称为ISNInitial Sequence Number不能简单地认为初始序号一定是一个纯随机数。实际操作系统会按照自身算法生成初始序号并考虑安全性以及旧连接报文混入新连接等问题。为了便于理解假设X 100那么客户端发送SYN1, ACK0, SEQ1004. 状态变化客户端状态从CLOSED进入SYN-SENT含义是同步请求已经发送正在等待服务器确认。5. 第一次握手表达的含义客户端实际上是在告诉服务器我希望与你建立 TCP 连接。 我的初始序号是 X。 如果你收到了这个 SYN 请通过确认号 X1 对它进行确认。八、第二次握手服务器发送 SYN 和 ACK服务器收到客户端的 SYN 后如果端口处于监听状态并且允许建立连接就会发送第二个报文段。1. 报文方向服务器 → 客户端2. 标志位SYN 1 ACK 1其中SYN 1表示服务器也需要向客户端同步自己的初始序号。ACK 1表示确认号字段有效服务器正在确认客户端发送的 SYN。因此第二次握手通常称为SYN-ACK或者SYN ACK3. 服务器序号SEQ Y服务器也会生成自己的初始序号Y。假设Y 200那么第二次握手的序号为SEQ 200这里不能写成SEQ Y 1因为服务器的 SYN 当前正占用序号Y。只有这个 SYN 被客户端确认后服务器后续发送的第一个普通数据字节才会从Y 1开始。4. 确认号ack X 1客户端第一次握手发送的是SEQ X SYN 1由于 SYN 消耗一个序号服务器接下来期望客户端使用X 1所以服务器返回ack X 1假设X 100那么ack 101它表达的含义是序号为 100 的 SYN 已经收到 下一次希望你从序号 101 开始发送。5. 状态变化服务器从LISTEN进入SYN-RCVD也常写为SYN-RECEIVED表示已经收到对方的 SYN 也已经发送自己的 SYN 和 ACK 现在等待客户端的最终确认。此时连接还没有在服务器一侧完全建立。6. 第二次握手表达的含义服务器是在告诉客户端你的 SYN 我已经收到。 你的初始序号是 X 因此下一次请从 X1 开始发送。 同时我的初始序号是 Y 请你对 Y 进行确认。完整字段为SYN 1 ACK 1 SEQ Y ack X 1九、第三次握手客户端发送最终 ACK客户端收到服务器的 SYN-ACK 后需要确认服务器的初始序号。1. 报文方向客户端 → 服务器2. 标志位在普通的主动打开与被动打开场景中SYN 0 ACK 1客户端已经在第一次握手中发送过 SYN因此第三次握手不需要再次设置 SYN。第三次握手的主要任务是确认服务器的 SYN所以ACK 13. 客户端序号SEQ X 1第一次握手中的 SYN 已经占用了客户端的序号X。因此客户端下一个可使用的序号为X 1例如X 100那么第三次握手SEQ 101第三次握手的序号不能写成Y因为X 属于客户端的发送序号空间 Y 属于服务器的发送序号空间TCP 是全双工协议两个方向分别维护独立的序号空间。客户端不能把服务器的序号Y当作自己的SEQ使用。4. 确认号ack Y 1服务器第二次握手发送的是SEQ Y SYN 1因为服务器的 SYN 消耗一个序号所以客户端下一次期望服务器使用Y 1于是第三次握手发送ack Y 1例如Y 200则ack 201它表示服务器序号为 200 的 SYN 已经收到 下一次希望服务器从序号 201 开始发送。5. 状态变化客户端成功处理服务器的 SYN-ACK并发送最终 ACK 后从SYN-SENT进入ESTABLISHED服务器收到这个最终 ACK 后从SYN-RCVD进入ESTABLISHED此时双方都确认了对方的初始序号连接正式建立。RFC 9293 将该过程定义为 TCP 建立连接时用于同步双方序号空间的基本三次握手。十、三次握手报文汇总握手方向SYNACKSEQack第一次客户端 → 服务器10X无效第二次服务器 → 客户端11YX 1第三次客户端 → 服务器01X 1Y 1用一行形式表示客户端 → 服务器 SYN1, ACK0, SEQX 服务器 → 客户端 SYN1, ACK1, SEQY, ackX1 客户端 → 服务器 SYN0, ACK1, SEQX1, ackY1十一、代入具体数值理解假设X 100 Y 200那么三次握手就是第一次握手客户端 → 服务器 SYN 1 ACK 0 SEQ 100 ack 无效服务器收到后知道客户端的 SYN 占用了序号 100因此下一次期望101第二次握手服务器 → 客户端 SYN 1 ACK 1 SEQ 200 ack 101客户端收到后可以确认服务器已经收到客户端序号为 100 的 SYN。 服务器自己的初始序号是 200。第三次握手客户端 → 服务器 SYN 0 ACK 1 SEQ 101 ack 201服务器收到后可以确认客户端已经收到服务器序号为 200 的 SYN。最终双方的数据起始序号分别为客户端下一次发送SEQ 101 服务器下一次发送SEQ 201十二、为什么必须进行三次握手1. TCP 需要同步两个方向的初始序号TCP 是全双工协议客户端可以向服务器发送数据 服务器也可以向客户端发送数据因此双方各自拥有独立的发送序号空间。连接建立时需要完成四件事1. 客户端把自己的初始序号 X 告诉服务器 2. 服务器确认客户端的 X 3. 服务器把自己的初始序号 Y 告诉客户端 4. 客户端确认服务器的 Y理论上可以拆成四个报文客户端 → 服务器SYNSEQX 服务器 → 客户端ACKackX1 服务器 → 客户端SYNSEQY 客户端 → 服务器ACKackY1但是服务器对客户端的确认和服务器自己的 SYN 可以合并成同一个报文SYN ACK于是四个逻辑动作被合并为三个 TCP 报文这就是“三次握手”。2. 两次握手无法确认服务器的 SYN 是否到达客户端假设只进行两次握手客户端 → 服务器SYN 服务器 → 客户端SYN ACK服务器发送完第二个报文后无法知道客户端是否真正收到了服务器的 SYN只有客户端发送第三个 ACK服务器才能确认客户端已经收到服务器的初始序号 Y。因此第三次握手不是多余的。它完成了服务器发送方向的序号确认。3. 三次握手可以降低旧连接报文造成的混乱网络中的报文可能发生延迟重复乱序重传假设某个旧连接中的 SYN 报文在网络中滞留过了一段时间后才到达服务器。服务器可能暂时把它当作新的连接请求并返回 SYN-ACK。但是原客户端当前并没有建立这个连接因此不会正常完成第三次握手或者会直接返回 RST。服务器不会仅凭一个旧 SYN 就把连接无条件视为完全建立。经典 TCP 规范在解释三次握手必要性时也强调了初始序号同步以及对网络中旧重复报文的处理问题。十三、三次握手和 SYN Flood 的关系SYN Flood 是一种针对 TCP 连接建立阶段的拒绝服务攻击。攻击者向服务器发送大量 SYN攻击者 → 服务器大量 SYN服务器收到 SYN 后会为连接请求保留一定状态发送 SYN-ACK进入SYN-RCVD等待客户端的第三次 ACK。如果攻击者使用伪造源地址或者故意不返回第三次 ACK服务器就会保留大量未完成连接。这些连接通常被称为半连接当半连接队列或相关资源被大量占用后正常客户端可能无法建立新连接。因此SYN Flood 的核心并不是网络中延迟的第二次握手不断产生僵尸连接。更准确的描述是攻击者发送大量不能完成三次握手的 SYN使服务器为大量半连接保留状态从而耗尽连接队列或系统资源。RFC 4987 专门描述了 SYN Flood 如何利用服务器为伪造或不完整连接保留状态并讨论了 SYN Cookie、增加队列容量和缩短超时等缓解方式。十四、第三次握手能否携带数据在最基础的教学模型中第三次握手通常被画成一个纯 ACKSYN 0 ACK 1 SEQ X 1 ack Y 1 数据长度 0这种情况下第三次握手不会消耗新的数据序号。但是从 TCP 报文结构上看第三次握手可以同时携带应用数据。假设客户端第三次握手携带 10 字节数据SEQ X 1 ack Y 1 数据长度 10那么这些数据占用的序号范围为X 1 X 10客户端下一次发送数据时序号变为X 11因此应当区分第三次握手通常不设置 SYN和第三次握手一定不能携带数据前者在普通三次握手中成立后者并不准确。十五、普通 ACK 为什么不消耗序号假设客户端发送纯 ACKSEQ 101 ACK 1 ack 201 数据长度 0由于没有 SYN 没有 FIN 没有应用数据所以该报文的序号空间长度为 0。客户端下一次发送数据时仍然可以使用SEQ 101也就是说纯 ACK 报文可以带有 SEQ 字段但它本身不会推动发送序号向前移动。会消耗序号的主要内容是应用数据按字节数消耗 SYN消耗 1 FIN消耗 1十六、SEQ 和 ack 的通用计算方法对于一个正常按序到达的报文段可以使用下面的思路计算确认号。定义序号空间长度 数据长度 SYN 是否置位 FIN 是否置位写成公式SEG.LEN DataLength SYN FIN其中SYN1 时计入 1否则计入 0 FIN1 时计入 1否则计入 0对方期望的下一个序号通常为ack SEQ SEG.LEN例如只有 SYNSEQ 100 SYN 1 FIN 0 数据长度 0 ack 100 1 101只有 10 字节数据SEQ 100 SYN 0 FIN 0 数据长度 10 ack 100 10 11010 字节数据加 FINSEQ 100 SYN 0 FIN 1 数据长度 10 ack 100 10 1 111不过需要注意这个公式适用于报文按序到达并能够推进接收端期望序号的场景。TCP 确认号的根本含义仍然是接收端下一次期望收到的序号如果报文乱序到达确认号不一定直接等于刚收到报文的SEQ SEG.LEN。十七、客户端和服务器分别维护独立序号空间三次握手中最容易出现的错误之一是把X和Y混在一起。实际上X 属于客户端 → 服务器方向 Y 属于服务器 → 客户端方向可以理解为双方各有一本独立的账本。客户端发送方向客户端 SYNSEQ X 客户端第一个数据字节SEQ X 1服务器通过ack X 1确认客户端的发送进度。服务器发送方向服务器 SYNSEQ Y 服务器第一个数据字节SEQ Y 1客户端通过ack Y 1确认服务器的发送进度。因此第三次握手绝对不能写成客户端 SEQ Y因为Y属于服务器的发送序号空间。十八、三次握手中的状态迁移客户端状态CLOSED | | 主动发起连接发送 SYN v SYN-SENT | | 收到合法 SYN-ACK发送 ACK v ESTABLISHED服务器状态CLOSED | | bind、listen v LISTEN | | 收到 SYN发送 SYN-ACK v SYN-RCVD | | 收到最终 ACK v ESTABLISHED完整状态变化阶段客户端状态服务器状态初始CLOSEDLISTEN第一次握手后SYN-SENT收到 SYN 前仍为LISTEN第二次握手后等待并处理 SYN-ACKSYN-RCVD第三次握手后ESTABLISHED收到 ACK 后进入ESTABLISHED在典型 Socket API 中客户端的connect()通常在连接建立成功后返回服务器的accept()通常从已完成连接队列中取得已经完成握手的连接监听套接字继续负责接收新的连接请求accept()返回的新套接字负责与具体客户端通信。十九、常见错误逐项纠正错误一第一次握手是 SYN1、ACK1错误写法第一次握手 SYN 1 ACK 1正确写法第一次握手 SYN 1 ACK 0因为客户端还没有收到服务器的初始序号没有需要确认的内容。错误二第一次握手没有确认号字段不准确写法第一次握手不存在确认号字段。准确写法确认号字段始终存在于 TCP 首部中 但由于 ACK0该字段没有协议意义。错误三第二次握手的 SEQ 是 Y1错误写法SEQ Y 1正确写法SEQ Y服务器当前发送的 SYN 正在占用序号Y。服务器后续第一个普通数据字节才使用Y 1。错误四第三次握手仍然设置 SYN1普通三次握手中的错误写法SYN 1 ACK 1正确写法SYN 0 ACK 1客户端已经在第一次握手中发送过 SYN。不过需要注意同时打开是 TCP 支持的一种特殊场景。两个端点都主动发送 SYN 时报文交换过程可能与普通客户端—服务器模型不同。本文讨论的是最常见的主动打开与被动打开过程。RFC 9293 明确说明三次握手同样支持双方同时主动打开。错误五第三次握手的 SEQ 是 Y错误写法客户端第三次握手 SEQ Y正确写法客户端第三次握手 SEQ X 1因为客户端必须使用自己的发送序号空间。Y是服务器的初始序号只能出现在服务器发送报文的 SEQ 字段或者被客户端用于计算确认号ack Y 1错误六确认号永远等于对方 SEQ1这个说法只适用于对方发送了一个不携带数据的 SYN因为 SYN 消耗一个序号。对于普通数据ack SEQ 数据长度对于携带 FIN 的数据ack SEQ 数据长度 1更加通用且准确的说法是确认号表示接收方下一次期望收到的序号。错误七SYN 相当于一个虚构的数据字节这种说法适合辅助理解但不够严谨。更准确的说法是SYN 不一定是应用层数据字节但它会占用 TCP 序号空间中的一个位置。因此SYN 的 SEQ X被确认后ack X 1错误八两次握手必然导致大量 SYN Flood 僵尸连接三次握手的主要作用是同步双方初始序号确认两个方向都具备收发能力减少旧重复连接请求造成的混乱。SYN Flood 则是攻击者利用服务器在SYN-RCVD状态保存半连接资源的行为。两者有关联但不能把“三次握手存在的全部原因”简单归结为防御 SYN Flood。事实上即使 TCP 使用三次握手仍然可能遭受 SYN Flood因此操作系统还需要使用SYN Cookie半连接队列管理超时与重传限制速率限制防火墙与负载均衡防护等额外机制。二十、最终结论标准的 TCP 三次握手可以概括为第一次握手 客户端 → 服务器 SYN1, ACK0, SEQX 第二次握手 服务器 → 客户端 SYN1, ACK1, SEQY, ackX1 第三次握手 客户端 → 服务器 SYN0, ACK1, SEQX1, ackY1需要牢牢记住以下规则1. ACK 标志位和确认号字段不是同一个概念。 2. ACK0 时确认号字段仍然存在但没有协议意义。 3. SYN 和 FIN 各自消耗一个序号。 4. 普通纯 ACK 不消耗新的序号。 5. ack 表示下一次期望收到的序号。 6. 客户端和服务器分别维护独立的发送序号空间。 7. X 属于客户端发送方向Y 属于服务器发送方向。 8. 普通三次握手的第三个报文是 ACK而不是再次发送 SYN。 9. 三次握手的核心是同步并确认双方的初始序号。 10. SYN Flood 利用了服务器保存半连接状态的机制但防御 SYN Flood 不是三次握手存在的唯一原因。只要理解了SEQ 是“我这次从哪里开始发送” ack 是“我下一次希望你从哪里开始发送”三次握手中的所有X、Y、X1和Y1就不再需要死记硬背而是可以根据 TCP 序号空间自然推导出来。