TCP 三次握手与四次挥手

TCP 三次握手与四次挥手 深入理解 TCP 三次握手与四次挥手从状态机到抓包实战一、引言连接的生命周期TCP 是面向连接的协议。在数据真正开始传输之前通信双方必须先建立一条虚拟通道——这就是三次握手Three-Way Handshake数据传输完毕后双方需要优雅地释放这条通道——这就是四次挥手Four-Way Wave。如果你用过 Wireshark 抓包一定见过 SYN、SYNACK、FIN 这些标志位如果你排查过线上问题大概率遇到过 TIME_WAIT 堆积或 CLOSE_WAIT 泄漏。本文的目标是从报文结构到状态机从理论到抓包把握手与挥手这件事彻底讲透。二、前置知识TCP 报文头部在理解握手之前必须先把 TCP 报文头部的结构印在脑子里。TCP 头部最小 20 字节最大 60 字节含选项核心字段速览字段位宽作用源端口 / 目标端口各 16 bit标识发送端和接收端应用进程序列号Sequence Number32 bit本报文段数据第一个字节的编号确认号Acknowledgment Number32 bit期望收到的下一个字节的序列号数据偏移4 bitTCP 头部长度 / 4即头部有多少个 32-bit 字标志位Flags6 bitURG / ACK / PSH / RST / SYN / FIN窗口大小16 bit接收窗口大小用于流量控制校验和16 bit校验整个报文段含伪首部紧急指针16 bitURG1 时有效指向紧急数据末尾六个标志位是握手与挥手的主角标志位全称含义SYNSynchronize请求建立连接同步序列号ACKAcknowledgment确认号字段有效除第一个 SYN 外都要置 1FINFinish发送方数据已发完请求释放连接RSTReset强制重置连接异常终止PSHPush提示接收方尽快将数据交付应用层URGUrgent紧急指针有效关键规则除第一个 SYN 报文外TCP 要求所有正常通信报文ACK1RST 报文除外——RST 是否带 ACK 取决于触发场景。因此四次挥手时所有正常报文ACK1区别在于FIN标志位的设置。三、三次握手逐包拆解3.1 报文层面拆解Step 1Client → Server[SYN]seq x Client 随机生成的初始序列号 ISN ack 0 ACK 标志位为 0确认号无意义 flags SYNClient 状态CLOSED → SYN_SENTServer 收到后分配半连接队列条目状态LISTEN → SYN_RCVDStep 2Server → Client[SYN, ACK]seq y Server 随机生成的 ISN ack x 1 确认 Client 的 SYN期望下一个字节序号为 x1 flags SYN | ACKServer 状态SYN_RCVD已收到 SYN已发出 SYNACKClient 收到后状态SYN_SENT → ESTABLISHEDStep 3Client → Server[ACK]seq x 1 Client 的第一个数据字节序号 ack y 1 确认 Server 的 SYN flags ACKClient 状态ESTABLISHEDServer 收到后状态SYN_RCVD → ESTABLISHED半连接条目移入全连接队列accept queue此时连接建立完成双方进入 ESTABLISHED可以开始传输数据。3.2 为什么是三次不是两次四次这是一个经典面试题答案的核心在于TCP 是全双工协议需要双方各自确认对方的发送能力和接收能力正常。两次握手Client 发送 SYN → Server 回复 SYNACK → 连接建立。但 Client 无法确认 Server 的接收能力是否正常Server 的 SYNACK 可能在网络中丢失Server 会一直维护半连接直到超时。更关键的是防止已失效的连接请求报文段突然又传到了 Server。如果只有两次握手一个在网络中滞留的旧 SYN 到达 Server 后Server 就会错误地建立连接。三次握手Client 的最后一次 ACK 确认了 Server 的 SYN双方都确认了对端的收发能力。同时也让 Client 有机会拒绝/忽略旧的 SYNACK不回复 ACK 即可。四次握手理论上可以拆成四次——Server 的 SYN 和 ACK 分开发送。但实际上 TCP 协议将其合并为一条报文SYNACK因为 SYN 和 ACK 之间没有时间依赖合并可以减少一次网络往返。3.3 序列号为什么要随机ISNISNInitial Sequence Number不是从 0 或 1 开始而是由一个基于时钟的随机算法生成。原因有三防止旧报文混淆如果 ISN 固定网络中滞留的旧 TCP 报文可能被误认为是新连接的合法数据防止序列号预测攻击如果 ISN 可预测攻击者可以伪造 RST 报文强制断开连接或注入伪造数据避免端口复用冲突(src_ip, src_port, dst_ip, dst_port)四元组可能被快速复用随机 ISN 确保前后连接不会混淆3.4 SYN Flood 攻击与 SYN CookiesSYN Flood 是经典的 DDoS 攻击方式攻击者发送大量 SYN 报文但不完成握手导致 Server 的半连接队列被占满正常用户的连接请求被拒绝。防御方案SYN CookiesSYN Cookies 的核心思想是无状态握手——Server 不在本地分配任何资源给半连接而是将连接信息加密编码到 SYNACK 的序列号 y 中cookie Hash(src_ip, src_port, dst_ip, dst_port, timestamp, secret_key) y cookie编码进 ISN当 Client 回复 ACK 时ack y 1 cookie 1Server 从ack-1中解码出 cookie 并验证其有效性。校验通过才正式分配连接资源。关键优势即使面对海量 SYN FloodServer 也不消耗内存只在收到合法的第三次 ACK 时才创建连接。Linux 内核参数net.ipv4.tcp_syncookies 1开启 SYN Cookies。当半连接队列溢出时自动启用。四、四次挥手逐包拆解TCP 连接是全双工的每个方向都需要独立关闭。四次挥手的本质是两个方向的两次 FINACK共四条报文。4.1 报文层面拆解Step 1Active Closer → Passive Closer[FIN, ACK]seq u 当前发送方已发送数据的最后一个字节序号 1 ack v 确认已收到的数据 flags FIN | ACKActive Closer 状态ESTABLISHED → FIN_WAIT_1含义“我的数据发完了但还可以收数据。”Step 2Passive Closer → Active Closer[ACK]seq v ack u 1 确认对方的 FIN flags ACKPassive Closer 状态ESTABLISHED → CLOSE_WAITActive Closer 收到后FIN_WAIT_1 → FIN_WAIT_2CLOSE_WAIT 是被动关闭方等待应用层调用 close()的状态。如果应用层迟迟不调用 close()连接会一直停留在 CLOSE_WAIT这就是生产环境中CLOSE_WAIT 泄漏的根源。Step 3Passive Closer → Active Closer[FIN, ACK]seq w Passive Closer 可能还在 Step 2 后发了一些数据 ack u 1 对方没有再发数据确认号不变 flags FIN | ACKPassive Closer 状态CLOSE_WAIT → LAST_ACKActive Closer 收到后FIN_WAIT_2 → TIME_WAITStep 4Active Closer → Passive Closer[ACK]seq u 1 ack w 1 确认对方的 FIN flags ACKPassive Closer 收到后LAST_ACK → CLOSEDActive Closer 进入 TIME_WAIT等待2MSL后自动进入 CLOSED4.2 TIME_WAIT 为什么是 2MSLMSLMaximum Segment Lifetime是报文段在网络中的最大存活时间RFC 793 建议为 2 分钟Linux 默认为 30 秒。2MSL 最大往返时间的两倍。TIME_WAIT 的存在有两个关键目的目的 1确保最后一个 ACK 被对方收到如果 Step 4 的 ACK 在网络中丢失Passive Closer 会重传 FINLAST_ACK 状态下。如果 Active Closer 已经进入 CLOSED它将无法处理这个重传的 FIN只能回复 RST导致 Passive Closer 收到错误而非正常关闭。TIME_WAIT 状态下Active Closer 可以接收重传的 FIN 并重新回复 ACK。目的 2防止旧连接的数据段混入新连接等待 2MSL 确保本连接产生的所有报文段都从网络中消失。这样当同一个四元组(src_ip, src_port, dst_ip, dst_port)被复用时不会收到上一个连接的幽灵报文。实际影响高并发短连接场景下如 HTTP 1.0 非 keep-alive主动关闭方通常是 Server会产生大量 TIME_WAIT 状态的连接。Linux 可通过以下参数优化net.ipv4.tcp_tw_reuse 1允许 TIME_WAIT 连接被复用仅客户端net.ipv4.tcp_fin_timeout调整 FIN_WAIT_2 超时时间4.3 CLOSE_WAIT 泄漏排查如果服务器上出现大量 CLOSE_WAIT 连接不释放说明应用程序收到对方的 FIN 后一直没有调用 close() 或 shutdown()。排查思路netstat -anp | grep CLOSE_WAIT确认数量和进程检查应用代码中read()返回 0对端关闭后是否正确调用了close()常见原因代码逻辑中忽略了read() 0的 EOF 场景或者资源清理异常处理不完整五、状态机全景TCP 连接共有 11 种状态。理解状态机是排障和面试的基础截图自 TCP Explorer 交互页面 的状态机模块状态一览状态含义典型场景CLOSED无连接初始 / 最终LISTEN监听中服务器等待连接SYN_SENT已发 SYN客户端 connect() 后SYN_RCVD已收 SYN 并回复服务器收到 SYN 后ESTABLISHED连接已建立数据传输中FIN_WAIT_1主动关闭已发 FINclose() 后FIN_WAIT_2已收到 ACK等待对方 FIN半关闭CLOSING双方同时关闭同时发送 FIN罕见TIME_WAIT等待 2MSL主动关闭方最终状态CLOSE_WAIT已收到 FIN等待应用 close()被动关闭方LAST_ACK被动关闭方已发 FIN等待最后 ACK同时打开与同时关闭虽然少见但 TCP 协议设计时就考虑了同时打开和同时关闭的场景同时打开双方都从 CLOSED 发出 SYN各自进入 SYN_SENT。收到对方的 SYN 后而非预期的 SYNACK进入 SYN_RCVD再各自回复 SYNACK。最终双方都进入 ESTABLISHED共交换 4 条报文。路径CLOSED → SYN_SENT → SYN_RCVD → ESTABLISHED。同时关闭双方同时发送 FIN从 ESTABLISHED 进入 FIN_WAIT_1。收到对方的 FIN 后直接进入 CLOSING跳过 FIN_WAIT_2各自回复 ACK 后进入 TIME_WAIT。路径ESTABLISHED → FIN_WAIT_1 → CLOSING → TIME_WAIT。六、实战Wireshark 抓包解读假设你用 Wireshark 抓到一个 TCP 流显示过滤tcp.stream eq 0你会看到类似这样的序列No. Src → Dst Flags seq ack Info 1 C → S SYN 100 0 Client → Server SYN 2 S → C SYN, ACK 200 101 Server → Client SYNACK 3 C → S ACK 101 201 Client → Server ACK (handshake complete) ... (data transfer with PSH, ACK) ... 100 C → S FIN, ACK 5000 8000 Client → Server FIN 101 S → C ACK 8000 5001 Server → Client ACK 102 S → C FIN, ACK 8000 5001 Server → Client FIN 103 C → S ACK 5001 8001 Client → Server ACK (final)截图自 TCP Explorer 交互页面 的抓包解析模块示例中 seq/ack 值的演变逻辑Client ISNx100Server ISNy200。握手完成后双方 seq 均1。假设数据传输阶段 Client 发送了 4900 字节seq 从 101 上升到 5000Server 发送了 7800 字节seq 从 201 上升到 8000所以 FIN 报文 seq5000/8000ack8000/5001。抓包时注意 seq 的增长反映的正是发送了多少字节数据。解读要点seq 和 ack 的关系ack 对方的 seq 1握手阶段因为 SYN 消耗一个序列号数据传输阶段ack 对方的 seq payload_length。SYN 消耗序列号ISN100 的 SYN 报文下一个数据字节从 101 开始。因此 Step 2 的 ack 是 101。FIN 也消耗序列号seq5000 的 FIN 报文ack 确认它是 5001。这是很多人混淆的点——FIN 虽然没有数据载荷但仍然占用一个序列号。四次挥手中间可能夹数据Step 2 的 ACK 和 Step 3 的 FIN 之间Passive Closer 还可以发送数据CLOSE_WAIT 状态下。七、总结维度三次握手四次挥手目的建立全双工连接释放两个方向的连接报文数3 条4 条可优化为 3 条如果双方同时关闭关键状态CLOSED → SYN_SENT → ESTABLISHEDFIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED标志位SYN → SYNACK → ACKFIN → ACK → FIN → ACK耗时1.5 RTT2 RTT 2MSL安全隐患SYN Flood → SYN Cookies 防御TIME_WAIT 堆积 / CLOSE_WAIT 泄漏面试高频 QA 速查问题一句话答案为什么三次握手不是两次两次握手无法防止已失效的连接请求到达服务端导致错误建立连接且无法让客户端确认服务端的接收能力SYN 报文为什么消耗序列号因为 SYN 需要被可靠确认消耗一个序列号才能用 ackx1 来确认它ACK 不需确认所以不消耗FIN 报文为什么也消耗序列号同理FIN 需要被对方确认占用一个序列号可以精确确认我收到了你的 FINTIME_WAIT 为什么是 2MSL1个 MSL 让最后的 ACK 到达对端1个 MSL 让对端重传的 FIN 到达本端合计确保所有残余报文消失CLOSE_WAIT 太多怎么排查netstat -anp | grep CLOSE_WAIT定位进程检查代码中read()0后是否调用了close()SYN Flood 怎么防御开启 SYN Cookiestcp_syncookies1、增大半连接队列、缩短 SYN Timeout、部署 SYN Proxy能否三次挥手就关闭连接可以如果被动关闭方在收到 FIN 时也没有数据要发送可以将 ACKFIN 合并为一条报文变成三次挥手TCP Fast Open 是什么在 SYN 报文中携带数据用 Cookie 验证省去一次 RTT将握手首次数据传输压缩到 1 RTT延伸阅读方向TCP Fast Open (TFO)在 SYN 报文中携带数据将握手 首次数据传输从 2 RTT 降到 1 RTTTCP keepalive长时间空闲连接的心跳探测机制QUIC 协议基于 UDP将握手 加密协商合并为 1 RTT甚至 0 RTT从根本上解决了 TCP 三次握手的延迟问题动手实践本文配图使用的 TCP Explorer 交互页面 是一个独立的 HTML 文件包含三个模块握手模拟器逐步骤推进三次握手和四次挥手观察状态变化状态机探索器悬停/点击 11 个 TCP 状态查看详细说明抓包解析器输入 seq/ack/标志位实时判断报文所处阶段您可以直接在下方操作交互页面无需下载在线体验TCP Explorer 交互工具原创技术博客转载请注明出处。所有配图和交互页面均为自绘可自由用于学习和分享。