本节摘要:TCP 用三次握手建立连接、序号与确认号把字节流编号、超时重传与快速重传兜底丢失、四次挥手优雅告别。本节用一条真实抓包逐段走完连接的完整生命周期,讲清每个机制解决的具体问题,以及 SYN 洪泛与 TIME_WAIT 两个工程名场面。
全册旅程最核心的一幕:TCP 如何在一条会丢、会乱、会重复的管道上,虚拟出"字节流无损有序到达"的服务。答案由四个机制组成:握手(对齐初始状态)、序号确认(给字节编号并回执)、重传(兜底丢失)、挥手(有序收尾)。
抓包实录(客户端 52738 → 服务器 443,括号内为关键字段): No.1 客户端 → 服务器 SYN seq=3581209233, win=64240, mss=1460 No.2 服务器 → 客户端 SYN,ACK seq=4152768901, ack=3581209234, win=28960 No.3 客户端 → 服务器 ACK ack=4152768902 逐包解读: 第 1 包:客户端随机起跑线 seq=x(避免与旧连接的序号混淆), 并附上自己的接收窗口与最大段长(mss)。 第 2 包:服务器做了两件事——确认(ack=x+1,"你的 x 我收到了, 下一个发 x+1")同时亮出自己的起跑线 seq=y。 第 3 包:客户端确认服务器的 y。至此双方都知道: 对方的初始序号、对方的接收窗口、双向通路都通。 为什么不能两次?若两次即可成连接,一个在网络里游荡的 旧 SYN(上次连接的残留)迟到抵达服务器,服务器直接开连, 单方面进入数据发送——客户端根本没想连。第三次 ACK 给了客户端否决权:不是我发起的,我不确认,连接流产。 历史实例"三次握手"论文里把这个场景叫" resurrection 的旧 SYN"。
握手完成后,双方进入 ESTABLISHED 状态,数据开始流淌。用状态机视角看客户端:CLOSED → SYN_SENT → ESTABLISHED;服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED。排错时这套状态名会出现在各种工具输出里(第 8 章 netstat 常客)。
TCP 把数据看作连续字节流,序号是字节的编号而非包的编号。发送 100 字节、序号从 1000 起,下一个包序号就是 1100——中间丢了哪个字节段,接收方的确认号会一直"卡"在缺口处。
可靠传输的四种典型剧情: 剧情 A 正常: 发送 seq=1000,100B → 接收方回 ack=1100("1100 之前都齐了") 剧情 B 数据丢失(触发超时重传): 发送 seq=1100,200B → 丢了。发送方等 RTO(重传超时)没等到 ack, 重发 seq=1100 → 对方收到回 ack=1300。 RTO 怎么定?基于往返时延的加权平均再加波动余量—— 太短会误伤(网络只是慢了不是丢了),太长会干等。 剧情 C 确认丢失(接收方幂等救场): 发送 seq=1300,100B → 收到,回 ack=1400,但这个 ack 丢了。 发送方重传 seq=1300 → 接收方发现这段已经有了,丢弃数据、 重发 ack=1400。幂等设计让重复数据无害。 剧情 D 连环到达(触发快速重传): 发送 1100、1300、1500、1700 四段,1300 丢失。 后三段到达但缺口在 1300 → 接收方连回三个 ack=1300 (重复确认)。发送方见到 3 个重复确认,不等 RTO 到点, 立即重传 1300——这就是快速重传,比干等超时快得多。
超时重传(等定时器)与快速重传(听重复确认信号)是一对搭档:前者兜底所有静默丢失,后者加速常见的高频场景。RTO 的自适应(RTT 采样、指数退避)让同一个 TCP 在局域网与洲际链路都能工作。
抓包实录(主动关闭方是客户端): No.20 客户端 → 服务器 FIN,ACK ("我发完了") No.21 服务器 → 客户端 ACK ("知道了,我可能还有没收尾的") No.22 服务器 → 客户端 FIN,ACK (半秒后:"我也发完了") No.23 客户端 → 服务器 ACK ("好,再见") ← 客户端在此后再等 2MSL(报文最大生存时间的两倍)才进 CLOSED 为什么四次而不是三次握手那样的三次? 因为关闭是两个独立方向的停止。收到对方 FIN 只说明 对方不发了,自己可能还有数据没吐完——ACK 先回, 自己的 FIN 等数据流干再发。中间的空档叫半关闭(half-close), shutdown 的写端不关读端就是这个状态。 为什么主动方要等 2MSL? 最后一发 ACK 自己没保险(没人再确认它),丢了的话对方会 重传 FIN,必须留着端口应答;同时等够时间让旧报文自然消亡, 不给下一个复用该端口的新连接投毒。

背景:某网站突然大量用户无法访问,服务器 CPU 不高、带宽正常。在网关抓包看到:
过滤 tcp.flags.syn == 1 and tcp.flags.ack == 0,每秒约 4 万个 SYN, 源 IP 高度随机,且几乎没有第三次握手的 ACK。 解读:SYN 洪泛——攻击者海量伪造 SYN,服务器为每个 分配半连接资源并回 SYN+ACK 等待第三次握手,永远等不到。 半连接队列被占满,真实用户的 SYN 被直接丢弃, 表现为"服务看起来活着但连不上"。 处置组合拳: - 开启 SYN Cookie:不为半连接预留状态,把状态编码进 SYN+ACK 的序号里,收到合法第三次握手再反解验证—— 队列不再是瓶颈 - 上游清洗伪造源(运营商协作) - 防火墙限速半连接(第 7 章的阵地) 变式:反过来,若看到大量正常客户端反复重传 SYN, 问题多半在服务器侧:accept 队列溢出(listen backlog 太小) 或应用 accept 太慢——攻击与自伤,表象相同,处方相反。
⚠️ 常见坑:服务器上数万个 TIME_WAIT 不必恐慌。这是主动关闭一方的正常等保险状态(2MSL 后自动回收)。真正要警惕的是 CLOSE_WAIT 大量堆积——那是应用代码忘了调用关闭,属于泄漏,调内核参数救不了。
可靠性的骨架立起来了,下一节补上它的两个限速器:流量控制与拥塞控制。