TCP 协议详解

传输控制协议(Transmission Control Protocol)· 依据 RFC 9293(STD 7,2022 年 8 月)编写
RFC 9293废止 RFC 793 / 879 / 2873 / 6093 / 6429 / 6528 / 6691更新 RFC 1011 / 1122 / 5961传输层 · 面向连接 · 可靠字节流

1.概述

TCP(Transmission Control Protocol,传输控制协议)是互联网协议栈中最重要的传输层协议。它运行于网络层 IP 协议之上,为应用层提供一种可靠的、有序的、面向连接的全双工字节流服务。HTTP/HTTPS、SSH、FTP、SMTP 等绝大多数应用层协议都构建在 TCP 之上。

1981 年发布的 RFC 793 首次完整定义了 TCP。此后四十余年间,大量修订散落在多个独立文档中。RFC 9293(2022 年 8 月,STD 7)将这些修订与 RFC 793 的原始规范汇集为一,成为当前 TCP 的唯一权威基础规范:它废止了 RFC 793 以及 879、2873、6093、6429、6528、6691 等修订文档,并更新了 RFC 1011、1122、5961。拥塞控制等复杂算法则有意留给 RFC 5681、6298、7323 等伴随文档规定。

1.1 TCP 提供的服务(RFC 9293 §2.2 关键概念)

1.2 连接与四元组

一条 TCP 连接由四元组唯一标识:(源IP, 源端口, 目的IP, 目的端口)。同一台服务器可以用同一个 443 端口同时服务成千上万的连接,因为每个连接的源 IP/源端口组合不同。TCP 报文段封装在 IP 数据报中传输:[ IP 首部 | TCP 首部 | 应用数据 ]。下文第 2 节将逐字段剖析 TCP 首部。

端口号约定(IANA):0–1023 为知名端口(HTTP 80、HTTPS 443、SSH 22、FTP 21、Telnet 23、SMTP 25、DNS 53);1024–49151 为注册端口;49152–65535 为动态/私有端口,客户端发起连接时通常从动态范围选取临时端口(ephemeral port)。

2.TCP 报文段格式(RFC 9293 §3.1)

TCP 首部固定部分 20 字节,加上可变长选项后最长 60 字节,其后才是应用数据。下图为交互式首部结构图(与 RFC 9293 图 1 一致):把鼠标悬停在任意字段上即可在下方说明栏查看该字段的详细解释;单击字段可以「钉住」当前说明,方便滚动阅读,再次单击取消。

源端口 Source Port16 bits
目的端口 Destination Port16 bits
序列号 Sequence Number32 bits
确认号 Acknowledgment Number32 bits
Data Offset4 bits
Reserved4 bits
CCWR
EECE
UURG
AACK
PPSH
RRST
SSYN
FFIN
窗口 Window16 bits
校验和 Checksum16 bits
紧急指针 Urgent Pointer16 bits
选项 Options(若有)可变,0–40 字节
数据 Data可变长
悬停查看说明 · 单击钉住 / 取消

2.1 字段速查表

字段位数要点
Source Port16发送方端口;四元组的一部分。
Destination Port16接收方端口;决定交付给对端哪个服务。
Sequence Number32本报文段第一个数据字节的序号;SYN=1 时为 ISN,首字节为 ISN+1。
Acknowledgment Number32ACK=1 时有效;期望收到的下一字节序号,即累计确认。
Data Offset4首部长度,单位 32 位字;5 = 20 字节,最大 15 = 60 字节。
Reserved4置 0 发送、收到忽略(RFC 793 为 6 位,ECN 借走 2 位)。
CWR / ECE1+1显式拥塞通知 ECN(RFC 3168):拥塞窗口已减小 / ECN 回显。
URG / ACK / PSH1×3紧急指针有效 / 确认号有效 / 立即推送。
RST / SYN / FIN1×3复位连接 / 同步序号(建连)/ 发送完毕(断连)。
Window16接收窗口 rwnd,流量控制;无符号数,可被窗口扩大选项左移。
Checksum16伪首部+首部+数据的反码和取反;强制生成、强制校验。
Urgent Pointer16URG=1 时有效;紧急数据末尾的序号偏移。
Options0–320DataOffset>5 时存在;EOL/NOP/MSS 必须支持。
Data可变应用数据;长度受 MSS、MTU、窗口约束。

2.2 校验和与伪首部(Pseudo-header)

计算校验和时,TCP 在首部之前概念性地拼上一个 96 位(IPv4)伪首部——它来自 IP 层,并不随报文传输。把源/目的地址纳入校验,可以防止报文段被错误路由后仍被接收方接受。IPv4 伪首部结构如下(悬停查看各字段):

Source Address 源 IP 地址32 bits
Destination Address 目的 IP 地址32 bits
zero8 bits
PTCL8 bits
TCP Length16 bits
图 2 · IPv4 伪首部(RFC 9293 Figure 2)
IPv6 情形:伪首部按 RFC 8200 §8.1 定义,共 320 位:128 位源地址 + 128 位目的地址 + 32 位上层报文长度 + 24 位零填充 + 8 位 Next Header。IPv6 下 TCP 校验和同样强制。

3.TCP 选项(RFC 9293 §3.2)

选项位于首部末尾,总长须为 4 字节的整数倍(不足时以 EOL/零填充)。RFC 9293 规定所有实现必须支持 EOL、NOP、MSS 三个选项(MUST-4);窗口扩大、SACK、时间戳定义于 RFC 7323 / 2018 等文档,是实现高性能 TCP 的事实标准。悬停下列选项卡片查看详情。

选项Kind长度用途与要点(悬停行查看详情)
EOL01 字节标记选项列表结束。
NOP11 字节填充对齐用,无实际语义。
MSS24 字节声明本端最大接收段长,仅 SYN 中出现。
WSOPT33 字节窗口左移 0–14 位,突破 64 KiB 上限。
SACK-Perm42 字节握手中声明支持选择确认。
SACK5可变报告已收到的非连续块,精确重传。
TSopt810 字节RTT 测量 + 防序号回绕(PAWS)。
完整选项注册表由 IANA 维护(RFC 9293 §6)
实现要求:① 必须能在任何报文段中接收选项(MUST-5);② 不认识的选项(带长度字段的)必须静默跳过(MUST-6);③ 遇到非法选项长度(如 0),建议复位连接并记录错误(MUST-7);④ 除 EOL/NOP 外,所有选项必须带长度字段,未来的新选项亦然(MUST-68)。

4.连接的建立:三次握手(RFC 9293 §3.5)

建立连接的本质是双方可靠地交换初始序列号(ISN)并确认对方收到。下图为标准的三次握手时序,悬停每个报文查看该步的细节与状态变迁。

客户端(主动打开)服务器(被动打开)
CLOSED → SYN-SENT① SYN · seq=xLISTEN
SYN-SENT② SYN+ACK · seq=y, ack=x+1→ SYN-RECEIVED
→ ESTABLISHED③ ACK · seq=x+1, ack=y+1→ ESTABLISHED
悬停报文查看各步细节

4.1 为什么必须是三次,而不是两次?

4.2 相关细节

5.连接的终止:四次挥手(RFC 9293 §3.6)

FIN 的含义是「我这一方数据发完了」。由于 TCP 全双工,两个方向要分别关闭,因此正常释放需要四个报文。任一方向关闭后另一方向仍可传输,称为半关闭(half-closed,§3.6.1)。悬停各报文查看细节。

主动关闭方被动关闭方
ESTABLISHED → FIN-WAIT-1① FIN · seq=uESTABLISHED
→ FIN-WAIT-2② ACK · ack=u+1→ CLOSE-WAIT
FIN-WAIT-2③ FIN · seq=w, ack=u+1→ LAST-ACK
→ TIME-WAIT (2MSL) → CLOSED④ ACK · ack=w+1→ CLOSED
悬停报文查看各步细节

5.1 TIME-WAIT 与 2MSL

5.2 真实抓包中的挥手变体:段数不够 4 不是异常

教科书是 4 个报文,真实抓包常常「不像」,因为 TCP 会合并报文(coalescing)

判断异常要看 RST、重传、零窗口,而不是「段数不够 4」。但注意:关闭不可能只有 2 段——少任何一个方向的 FIN 或它的 ACK 都关不完,TCP 没有「快速挥手」机制。

5.3 只关一半之后:FIN-WAIT-2 有兜底,CLOSE-WAIT 没有

RST 式拆除:除 FIN 的正常释放外,任何时刻一方都可发 RST 立即中止连接(如应用设置 SO_LINGER=0 后 close,或向未监听端口发起连接被内核拒绝)。RST 不经过 TIME-WAIT,缓冲中未发出的数据被直接丢弃。

6.TCP 状态机(RFC 9293 §3.3.2)

一条连接在任一时刻处于 11 种状态之一,状态随「用户调用 / 报文到达 / 超时」三类事件迁移。下图为完整状态迁移图:实线为正常路径,虚线为较少见路径;悬停任一状态节点查看其含义,迁移条件标注在箭头上(recv = 收到,send = 发送)。

主动打开 send SYN 被动打开 passive OPEN recv SYN · send SYN+ACK recv SYN · send SYN+ACK(同时打开) recv SYN+ACK · send ACK recv ACK 主动关闭 send FIN recv FIN · send ACK close · send FIN recv ACK recv FIN · send ACK recv FIN+ACK · send ACK recv FIN · send ACK recv ACK close · send FIN recv ACK 等待 2MSL 超时 close CLOSED SYN-SENT LISTEN SYN-RECEIVED ESTABLISHED FIN-WAIT-1 CLOSE-WAIT FIN-WAIT-2 CLOSING TIME-WAIT LAST-ACK
图 · TCP 状态迁移图(对应 RFC 9293 §3.3.2 图)

6.1 状态迁移速查表

当前状态触发事件动作下一状态
CLOSED主动 OPEN发送 SYNSYN-SENT
CLOSED被动 OPEN(listen)LISTEN
LISTEN收到 SYN发送 SYN+ACKSYN-RECEIVED
LISTENCLOSECLOSED
SYN-SENT收到 SYN+ACK发送 ACKESTABLISHED
SYN-SENT收到 SYN(同时打开)发送 SYN+ACKSYN-RECEIVED
SYN-RECEIVED收到 ACKESTABLISHED
SYN-RECEIVEDCLOSE(少见)发送 FINFIN-WAIT-1
ESTABLISHEDCLOSE发送 FINFIN-WAIT-1
ESTABLISHED收到 FIN发送 ACKCLOSE-WAIT
FIN-WAIT-1收到 ACKFIN-WAIT-2
FIN-WAIT-1收到 FIN(同时关闭)发送 ACKCLOSING
FIN-WAIT-1收到 FIN+ACK发送 ACKTIME-WAIT
FIN-WAIT-2收到 FIN发送 ACKTIME-WAIT
CLOSING收到 ACKTIME-WAIT
CLOSE-WAITCLOSE发送 FINLAST-ACK
LAST-ACK收到 ACKCLOSED
TIME-WAIT2MSL 超时CLOSED

注:除 RST 触发的迁移外,表中列出正常迁移;任何状态收到合法 RST 都会中止连接回到 CLOSED/LISTEN。

6.2 RST 的生成与处理(RFC 9293 §3.5.2–3.5.3 要点)

何时生成 RST:凡收到「不属于任何已知连接」的非 RST 报文段(如发到未监听端口的 SYN、半开连接上收到的数据段),原则上都应回送 RST。RST 的序号/确认号取值分两种情形,目的都是让对方能立即采信:

收到 RST 如何处理(吸收 RFC 5961,防盲注攻击):复位前先校验序号——RST 的序列号恰好等于 RCV.NXT 才直接拆除连接;落在接收窗口内但不等于 RCV.NXT 的,只回送一个 challenge ACK 请对方重发正确的 RST,连接保持不动;窗口之外的直接丢弃。攻击者要盲猜中 32 位序号里的唯一正确值,概率约 2⁻³²,伪造 RST 断连的门槛由此大幅抬高。

RFC 9293 对 RFC 5961 的一处澄清SYN-RECEIVED 状态收到合法 RST 时,若连接源自被动打开,应回到 LISTEN 继续等待新连接,而不是直接进入 CLOSED——这也是 RFC 9293 相对旧文档为数不多的实质改动之一。

7.可靠传输机制(RFC 9293 §3.4 / §3.8)

TCP 在不可靠的 IP 之上提供可靠字节流,依赖四根支柱:编号与确认、超时重传、快速重传、差错检测

7.1 字节编号与累计确认

抓包视角的黄金等式:Wireshark 里的 Next Seq 不是首部字段,是它按 Seq + Len + SYN/FIN 各占 1 帮你算出的派生值(线上只传 Seq 和 Ack)。健康连接中恒有「接收方回的 Ack == 发送方的 Next Seq」——拿它逐包验算,丢没丢包一眼可见。完整示范见第 11 节。

7.2 超时重传与 RTO 估算(§3.8.1,算法见 RFC 6298)

每发出一个含数据的段即启动重传定时器,超时(RTO)未收到确认则重传。RTO 不能拍脑袋定:太小导致不必要的重传加剧拥塞,太大则丢包恢复缓慢。标准做法是 Jacobson/Karels 算法,动态跟踪 RTT 的均值与波动:

SRTT ← (1 − α) · SRTT + α · R′   # 平滑 RTT,α = 1/8
RTTVAR ← (1 − β) · RTTVAR + β · |SRTT − R′| # RTT 偏差,β = 1/4
RTO = SRTT + 4 · RTTVAR      # 下限 1 秒(RFC 6298)

7.3 快速重传(Fast Retransmit)

接收方收到失序段时会立即重发对已收妥字节的确认(重复 ACK)。若发送方连续收到 3 个重复 ACK(dupthresh = 3),说明某个段大概率已丢失而非仅仅延迟——于是不等超时立即重传该段。快速重传把丢包恢复时延从一个 RTO 缩短到一个 RTT 量级,通常与快速恢复(§9)配合。

7.4 差错检测

7.5 其他可靠性相关的规范要点

8.流量控制:滑动窗口(RFC 9293 §3.8.6)

每个 ACK 报文中的 Window 字段都在告诉发送方:「从确认号开始,我还能再收 rwnd 个字节」。发送方据此维护发送窗口,任何时刻未确认数据量不得超过对方通告的窗口——这就是滑动窗口流量控制,它保证快速发送方不会淹没慢速接收方。悬停下图各区域查看含义。

① 已发送 · 已确认seq < SND.UNA
② 已发送 · 未确认SND.UNA ≤ seq < SND.NXT
③ 可发送(可用窗口)SND.NXT ≤ seq < SND.UNA+SND.WND
④ 暂不可发送seq ≥ SND.UNA+SND.WND
▲ SND.UNA(最老未确认)    ▲ SND.NXT(下一待发)        ▲ SND.UNA + SND.WND(窗口右缘)
发送窗口随每个到达的 ACK 向前滑动:收到新确认 → 左缘右移(① 区扩大);窗口更新 → 右缘右移(③ 区扩大)

8.1 窗口管理的关键问题

发送量上限公式:发送方任意时刻的在途数据上限 = min(拥塞窗口 cwnd, 接收窗口 rwnd)。rwnd 保护接收方,cwnd 保护网络,两者取小。

9.拥塞控制(RFC 9293 §3.8.2 → RFC 5681 / 6298)

RFC 9293 有意不收录完整的拥塞控制算法(§1:让基础规范可与多种算法组合),只要求实现必须支持某种拥塞控制,并指向伴随文档。以下是经典算法(RFC 5681「TCP 拥塞控制」+ RFC 6298「RTO 计算」)的框架,现代实现(CUBIC、BBR)在此基础上演进。

9.1 核心状态变量

9.2 四个经典机制

机制工作方式
慢启动
Slow Start
连接开始(或超时重传后)cwnd 从 1–10 个 MSS(IW,RFC 6928 允许 10)起,每收到一个 ACK 增加 1 MSS——cwnd 每 RTT 翻倍,指数增长,快速逼近可用带宽,直到达到 ssthresh。
拥塞避免
Congestion Avoidance
cwnd ≥ ssthresh 后改为线性增长:每 RTT 约增加 1 MSS(每 ACK 增加 MSS×MSS/cwnd)。加性增大、缓慢探测剩余带宽。
快速重传
Fast Retransmit
收到 3 个重复 ACK 立即重传丢失段,不等 RTO 超时(见 §7.3)。
快速恢复
Fast Recovery
快速重传后:ssthresh ← cwnd/2,cwnd ← ssthresh(「乘性减小」),直接进入拥塞避免而非慢启动——个别丢包不丢弃辛苦探测到的带宽估值。

9.3 丢包即拥塞信号:AIMD

现代演进:Linux 默认 CUBIC(RFC 8312,按三次函数增长,适合高速长距链路);Google BBR 不再把丢包当唯一拥塞信号,而是测量带宽与时延主动控制发送速率。它们都运行在 RFC 9293 规定的基础语义之上。
丢包之外的拥塞信号:ECN(RFC 3168)。丢包是「事后」信号——队列已经满了才被迫知晓。ECN 让路由器在队列将满、尚未丢包时就提前打招呼,完整闭环用到首部的 CWR/ECE 两位(§2):① 协商——客户端 SYN 同时置 ECE+CWR,服务端 SYN+ACK 置 ECE,双方确认启用;② 数据段的 IP 首部置 ECT 表示「本端支持 ECN」;③ 拥塞的路由器不丢包,改在 IP 首部打 CE(Congestion Experienced)标记;④ 接收方在后续 ACK 上持续置 ECE 回显,直到看见⑤发送方把 cwnd 减半(视同发生一次丢包)并置 CWR 告知「已响应拥塞」为止。发送方在一个窗口内无论收到多少 ECE 都只降速一次。

10.专题:MTU/MSS、粘包与常用观察手段

10.1 MSS、MTU 与分片

10.2 「粘包」不是 TCP 的问题

TCP 是字节流协议,不保留应用层消息边界:一次 send 可能被拆成多个段,多次 send 也可能被 Nagle 合并进一个段。期望按「条」收消息是应用层的责任,常见做法:① 定长消息;② 分隔符(如 HTTP/1.x 的 \r\n\r\n);③ 长度前缀(如先发送 4 字节长度)。UDP 才是保留报文边界的协议——这也是「面向报文」与「面向字节流」的本质区别。

10.3 观察 TCP 的常用命令

ss -tanp       # 查看各状态的 TCP 连接(TIME-WAIT / CLOSE-WAIT 排查必备)
ss -ti        # 查看单条连接的 cwnd、RTT、重传等内部信息
netstat -s      # 重传、段数等全局统计
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' # 抓握手/挥手/RST 报文
tcpdump -i eth0 -s 0 -w out.pcap    # 抓全量写文件:snaplen=0 不截断(否则正文只留片段,提示 Packet size limited during capture)

经验:服务器大量 CLOSE-WAIT → 应用忘记 close;大量 TIME-WAIT → 本端在主动关闭短连接,考虑长连接复用;大量 SYN-RECEIVED → 疑似 SYN 泛洪。Wireshark 侧的过滤器与专家信息读法见第 11 节。

11.抓包实战:用 Wireshark 读懂 TCP

前面讲的是规范怎么写,这一节看线上长什么样。内容来自对真实生产抓包(内网监控终端 ↔ 负载均衡,单条连接 66 个报文)的完整复盘,重点收录初学者最容易犯晕的几个点,与前面的规范条文一一对应。

11.1 先钉死三个数:Seq / Next Seq / Ack

Next Seq = Seq + Len + (SYN 占 1 ) + (FIN 占 1 ) # Len = 载荷字节数 tcp.len;纯 ACK 不推进 Next Seq
Ack      = 已连续收到的最高字节 + 1       # 相对对方的发送流
黄金等式:接收方回的 Ack == 发送方的 Next Seq # 健康连接里逐包成立

全双工意味着两本账、四个数同时跑:客户端的 Seq 由服务端的 Ack 追踪,服务端的 Seq 由客户端的 Ack 追踪,互不干扰。看到 C→S 一行的 Ack 在变化,那是客户端在确认「服务端的数据收到哪了」,与这一行自己的 Seq 无关——别被它搞懵。

11.2 一轮真实事务的逐包验算(悬停每一行看剧情)

以下是一条健康连接(HTTP over TCP,Wireshark 相对序号视图)的三次握手与第一轮请求/响应。拿黄金等式逐行验算:每个「对方回的 Ack」都精确等于「你的 Next Seq」。

#方向标志SeqLenNext SeqAck速读(悬停看详解)
46826C→SSYN0010敲门,SYN 占 1 号
46827S→CSYN,ACK0011应门,确认 ISN
46828C→SACK1011闭环,账本对齐
46829C→SACK1138813891请求首段,顶满 MSS
46830C→SPSH,ACK138924716361尾段带 PSH,共 1635 B
46831S→CACK1011636一个 Ack 顶两段 ✓
46832S→CACK1138813891636响应首段
46833S→CPSH,ACK13891614051636响应中段 16 B
46834S→CPSH,ACK14055141016365 B = chunked 终止块
46835C→SACK1636016361389确认首段
46836C→SACK1636016361405确认中段
46837C→SACK1636016361410闭环 ✓ 全收
一轮完整事务 = 请求(1~2 段) → 服务端累计 ACK → 响应(正文+5B 收尾) → 客户端 ACK

这条连接按同样的流水线跑了 9 轮半(客户端共发 15008 B,服务端回 11402 B),全程零重传、零 RST、Ack 严格单调——教科书级健康。最后一包(#48594,又一个 1388 字节请求首段)没等到 ACK:不是丢了,是抓包在这一刻停了,服务端的确认没来得及被记录。看抓包下结论前,先看抓包窗口。

11.3 一开始最容易晕的 8 个点

疑问正解
Next Seq 是 TCP 首部里的字段吗?不是。它是 Wireshark 的派生显示值,线上只有 Seq 和 Ack 两个真实字段(§2)。
SYN/FIN 不带数据(Len=0),凭什么占序号?占了序号才能被 ACK 确认、丢了才能重传——可靠性对控制报文同样适用(§7.1)。
ACK 还会被再 ACK 吗?不会。纯 ACK 不消耗序号、也不触发对方回复,否则 ACK→ACK 无限套娃(ACK 风暴)。只有消耗序号的段——数据(Len>0)、SYN、FIN——才会被确认。
看到 PSH 就是有数据吗?基本可断定(纯 ACK 绝无 PSH);但权威判据是 tcp.len > 0。反过来不成立:有数据未必带 PSH,分段发送时通常只有最后一段带。
抓包里 Seq 怎么从 0 开始?ISN 不是随机的吗?那是相对序号——Wireshark 默认把 ISN 减掉方便阅读。真实值在 tcp.seq_raw 里,是大随机数,用于防序号预测攻击(§4.2)。
挥手怎么只有 3 个包?是不是异常?FIN/ACK 合并优化,正常(§5.2);但 2 段永远关不完——两个方向各需一次 FIN 和它的 ACK。
客户端的 ACK 插在服务端两段响应中间,乱序了?没有。全双工两个方向各跑各的,响应正文与收尾块之间有点时间差,对方先把已收到的确认了——交错是正常呼吸。
抓不到 FIN,或最后的数据没有 ACK?多半是抓包提前停了(或 snaplen 太小截断),先看抓包窗口和 [Packet size limited during capture] 提示,别急着判异常。

11.4 Wireshark 排障三板斧

① 显示过滤器(逻辑运算:and/&&or/||not/!;混用时务必加括号,and 优先于 or):

过滤器作用
tcp.len > 0只看带载荷的包,滤掉占大半的纯 ACK——看数据时强烈推荐
tcp.stream == N单条连接的全部报文;也可右键包 → 追踪流 → TCP 流 直接读内容
tcp.flags.syn==1 and tcp.flags.ack==0纯 SYN(新建连接请求);tcp.flags.fin==1 / tcp.flags.reset==1 筛挥手/异常复位
tcp.analysis.retransmission超时重传(铁证:序列号与之前某包相同)
tcp.analysis.fast_retransmission快速重传(3 个重复 ACK 触发,§7.3)
tcp.analysis.duplicate_ack / .out_of_order重复 ACK(重传前兆)/ 乱序包
tcp.analysis.zero_window / .window_full零窗口(接收方处理不过来)/ 发送窗口填满(§8.1)

② 专家信息(分析 → 专家信息):Warning 级重点看 [TCP Retransmission]D-SACK(对方报告「早收过了」,说明存在不必要的重传——ACK 丢失或 RTO 太激进);Note 级的 (Suspected) spurious retransmission 是伪重传(数据其实到了,ACK 丢了导致误判);ACKed segment not captured 只是抓包起点问题,正常现象

③ 量化判读:重传率 > 1%~2% 需警惕,> 5% 基本可断定链路丢包;常见元凶是 Wi-Fi 干扰、带宽跑满、对端过载、中间设备丢包。统计 → TCP 流图形 → Time-Sequence (Stevens) 里,重传表现为「往回掉再跟上」的锯齿。

11.5 生产案例剪影:负载均衡与 CLOSE_WAIT

11.6 抓包判读进阶:文件质量、窗口定位与看包顺序

11.1–11.5 都默认抓包是干净完整的,真实世界常常不是:镜像口把同一份包交错复制了好几遍、tcpdump 默认 snaplen 只留 64 字节、链路还有乱序。这一节处理三件事——抓包本身可不可信、窗口归零该查谁、按什么顺序看包。要点来自一次带重复镜像包与 64 字节截断的真实抓包复盘。

① 先给抓包「体检」:文件质量决定判读可信度

文件问题判读时的折扣结论
重复镜像包同一份包交错重复多遍,包数虚高去重后可看连接/方向,但补不回漏抓
snaplen 截断(64 B 常见)只留头部、载荷残缺 → Len 值失真Len≠应用真实长度;重组/专家分析可信度下降
乱序 / 丢抓序号推断与重组失真别把分析提示直接当线上故障

② 红色提示的正确读法:分析提示 ≠ 线上字段

方括号里的 [TCP …] 是 Wireshark 依序号关系推算出来的注解,不是 TCP 首部里真实携带的字段。在脏抓包里尤其要降级看待:

提示真实含义
ACKed unseen segmentACK 确认到了抓包未见的数据——多为抓包起点/漏看问题
Previous segment not captured前面的数据段没抓到——FIN 带此提示也正常
Malformed Packet / Bogus IP length解析器失去上下文所致,脏抓包里不一定是真故障

③ Zero Window ↔ Window Update:先分清是谁喊的

④ 到 Linux 上印证:ss -tinp

ss -tinp  # -i 显示内部信息,Recv-Q / Send-Q 与抓包互相印证
Recv-Q 持续很大 → 本机 socket 里堆着数据等应用读 = 应用读得慢(对应抓包里它发 Win 变小/归 0)
Send-Q 持续变大 → 本机数据发不出去 = 对端 Zero Window / 网络拥塞 / 重传阻塞

判断方向看发出了 Win=0:发 Win=0 的那一端是接收方,优先检查它的应用与接收缓存。

⑤ 看包的推荐顺序(别一上来就盯红色提示)

动作
1先看 Source / Destination,确定方向
2看 SYN、SYN-ACK、ACK,确认是否从连接建立抓到(没有 SYN 多半是抓包起点晚于建链,不能据此断定连接没建立)
3按方向分别跟踪 Seq(Seq 只跟自己方向的上一次比)
4用对方方向的 Seq + Len 验证 Ack(黄金等式:Ack == 对方 Next Seq)
5看 Win,判断接收窗口是否变小或归 0(归 0 查接收方)
6最后再看 Wireshark 的红色专家提示(先排除抓包质量,再谈故障)

12.发送速率、窗口与 RTT:从流量控制到数据中心微突发

§8 讲了窗口怎么滑、§9 讲了 cwnd 怎么涨。这节把「窗口」和「实际能发多快」彻底串起来,并延伸到数据中心里绕不开的微突发(microburst)——它正是交换机 buffer、ECN、PFC/ETS 调优要压的场景。

12.1 发送端是「窗口受限」的:在途数据永远 ≤ W

§8.1 已给出在途上限 min(cwnd, rwnd)。把接收窗口 rwnd 记作 W(不失一般性,取瓶颈窗口),则任意时刻「已发未确认(在途)」的字节数 ≤ W。于是 TCP 会连续发送数据,直到在途量(FlightSize)达到发送窗口 W,窗口就满了,必须等 ACK 回来才能发下一个 W——这就是「窗口受限」。

关键直觉:线速 C 只是「那一下的瞬时峰值」;真正约束持续发送节奏的是 ACK 作为发送时钟返回的节奏(ACK Clock),而不是接口的线路速率。

12.2 持续发送速率由窗口与 RTT 决定,其上限再受线速约束

ACK 为 TCP 提供发送时钟(ACK Clock),窗口 W 决定每个 RTT 能发送的数据量,因此持续发送速率为 W/RTT。 一个 RTT 内,发送方随 ACK 的节奏连续注入数据、累计正好一个窗口 W,所以平均持续速率 = W / RTT。它与线速的关系由恒等式统一:

W = (W / RTT) · RTT = 速率 × 时延 = 在途量(BDP) # W 是「在途预算」,RTT 是「周转周期」,二者是同一笔库存的两种说法,没有重复计数

下图标出时序:t=0 以线速发出 W → 耗时 D 到达对端 → ACK 再耗时 D 返回 → 直到 t=RTT 窗口才解锁、发下一 W。所以「速率」的分母必须用往返 RTT,而不是单程 D——决定能不能再发的,是 ACK 回来的整段往返时刻。

量级体会(反例):机柜内 RTT≈10µs、窗口 W=2KB,则 W/RTT = 2KB/10µs ≈ 200 MB/s,而 100G 线速 C = 12.5 GB/s。即便窗口只有 2KB,W/RTT远在线速之下——这说明「窗口派生速率」与「线速」是两回事:前者是 ACK 时钟决定的可持续注入速率,后者是物理出口上限;到底哪个是瓶颈,要把 W/RTT 与 C 比一比才知道(见 12.3)。
窗口翻转:为何速率分母是 RTT 发送方 接收方 发 W·线速 C ACK 到→再发 W 收 W (t=D) W 在途·耗时 D ACK 返回·耗时 D → 解锁下一窗口 t=0 D RTT=2D RTT+D 2·RTT
呼应流体模型:这个 W/RTT 正是拥塞控制 / 排队方程里每条流的「到达速率」项(见 12.4 的 dq/dt 与 ZRL 闭式)。模型从第一性原理起就把 ACK 限速焊死在分母里。

12.3 实际吞吐 = min(窗口派生速率, 线速)

窗口推导出的可持续发送速率与链路线速是两个上界,二者取小:

吞吐 = min( W / RTT , C ) # 窗口允许速率 vs 链路物理上界,谁小谁生效
常见误解(数据中心版):不是「RTT 低 → W/RTT ≈ 线速 C」,而是「RTT 低 → BDP 极小 → 正常窗口 W ≫ C·RTT → W/RTT 远大于 C,被线速到 C」。W/RTT 是线速的上万倍,单流跑满 C 是因为线速天花板压住了它,不是因为它凑巧等于 C。
含义 / 数据中心典型值
BDP = C × RTT管道里「在途」的数据量。100G 线速、RTT≈10µs → BDP≈122 KB(极小)。
W(发送窗口)常见 MB 级(窗口扩大选项)。故 W ≫ BDP,窗口足够大,真正限制吞吐的是链路线速,而非发送窗口。
W / RTT窗口推导出的可持续发送速率,在 DC 中 ≫ 线速 C。
实际吞吐= C(被线速夹住);但聚合到达 N·W/R 远超 C,这正是微突发的根源。

12.4 数据中心低 RTT 为何让「微突发」更凶(incast)

微突发:许多条流在同一时刻把流量怼进同一个出口,队列在微秒级暴涨、超过交换机 buffer,来不及被 TCP 降窗反应就丢包。数据中心低 RTT 非但没「治好」它,反而喂肥了它:

用排队方程一句话说清(流体模型,Misra–Gong–Towsley SIGCOMM'00 框架):

dq/dt = N·W/R − C·I(q>0) # 到达(N 条流各 W/R) 远超 离开(C) → 队列增长

闭式 incast 模型(Zhang–Ren–Lin, INFOCOM'11,逐 RTT 同步分析)给出单流排队上限:

Q_i = min[ ( N·W_i − C·D − φ )⁺ , B ] # (·)⁺=max(·,0);C·D 即出口能即时排掉的在途量(BDP基线),φ 为抖动;B 为 buffer
边界(很重要):以上针对 TCP 这类窗口受限流。若是 UDP / RoCEv2,没有 TCP 意义上的滑动窗口和 ACK Clock,W/RTT 这个词都不存在——它们直接以 C 猛灌(进 N·C、出 C)。RDMA/RoCEv2 的微突发更凶,因为没有 TCP 自时钟去「平滑」发送节奏,这正是你交换机上 PFC/ETS、qos burst-mode、ECN 阈值要压的场景。

回到 §8(窗口滑动)与 §9(cwnd):微突发本质是「聚合到达 N·W/R 压垮单出口 C」,而 §9 的加性增大/乘性减小(AIMD)降窗是这个压力的唯一反馈回路——可惜在 DC 微秒级突发里它慢了整整一个 RTT。

动手实验 →理论嚼够了?戳这个 Microburst Buffer Lab(交互实验):自己调输入速率、同步、LACP 出口与 buffer 阈值,实时看见「微突发如何把队列推向边缘」,把上面 §12.4 的排队方程变成可拖的滑块。

12.5 一页速记

结论一句话
窗口受限在途 ≤ W;TCP 连续发送直到 FlightSize 达 W,此后须等 ACK 才能续发。
持续速率平均 = W / RTT;瞬时峰值可达线速 C。
恒等式W = 速率 × 时延 = 在途量(BDP),无重复计数。
实际吞吐= min(W/RTT, C);DC 中 W/RTT ≫ C,被夹到 C。
微突发低 RTT → 小 BDP,而 DC 交换机 buffer 通常也较小,因此更容易发生微突发;N·C 进、C 出 → µs 内爆。
模型闭环dq/dt = N·W/R − C·I(q>0);Q_i = min[(N·W_i − C·D − φ)⁺, B]。

13.术语与符号速查(RFC 9293 §3.3 / §4)

术语含义
TCB传输控制块:一条连接的全部状态(两端地址端口、序号变量、窗口、定时器等)的数据结构。
ISN初始序列号,握手时各自选取;应随机化(§3.4.1)。
SND.UNA最老的未确认字节序号(发送窗口左缘)。
SND.NXT下一个待发送字节序号。
SND.WND对端通告的发送窗口。
ISS本端初始发送序号(本端 ISN)。
RCV.NXT期望收到的下一字节序号(= 即将发出的确认号)。
RCV.WND本端接收窗口(通告给对方的窗口值)。
IRS对端初始序号(对端 ISN)。
MSS最大报文段数据长度(不含首部),SYN 中经选项通告。
MSL最长报文寿命,TIME-WAIT 等待 2×MSL。
RTT / SRTT / RTTVAR往返时延 / 平滑 RTT / RTT 偏差,用于 RTO 估算。
RTO重传超时时间,= SRTT + 4·RTTVAR(下限 1 s)。
cwnd / ssthresh拥塞窗口 / 慢启动阈值(RFC 5681)。
rwnd接收窗口,即首部 Window 字段通告的值。
SACK选择确认(RFC 2018),报告非连续已收块。
PAWS防序号回绕,基于时间戳(RFC 7323)。
ECN显式拥塞通知(RFC 3168),对应 CWR/ECE 标志位。
PMTUD路径 MTU 发现(RFC 1191 / 8201)。
half-closed半关闭:一个方向已 FIN 关闭,另一方向仍可传输。
half-open半开放:一端崩溃重启后遗留的异常连接,由 RST 清理。
四元组(源IP, 源端口, 目的IP, 目的端口),唯一标识一条连接。
dup ACK重复确认;连续 3 个触发快速重传。
ZWP零窗口探测(persist timer 发出),打破零窗口死锁。
捎带确认反向数据报文顺带携带 ACK 标志与确认号,节省报文。

14.参考文献

编号内容
RFC 9293Transmission Control Protocol (TCP),STD 7,2022-08。本文的主要依据。
RFC 793TCP 原始规范(1981),已被 RFC 9293 废止。
RFC 1122互联网主机要求——通信层;延迟 ACK、SWS 对策、Keep-Alive 等(被 9293 更新吸收)。
RFC 5681TCP Congestion Control:慢启动、拥塞避免、快速重传、快速恢复。
RFC 6298Computing TCP's Retransmission Timer:RTO 算法(SRTT/RTTVAR/Karn)。
RFC 7323TCP Extensions for High Performance:窗口扩大、时间戳、PAWS。
RFC 2018 / 2883TCP Selective Acknowledgment Options / SACK 扩展(D-SACK)。
RFC 3168The Addition of Explicit Congestion Notification (ECN) to IP:CWR/ECE 标志位。
RFC 5961Improving TCP's Robustness to Blind In-Window Attacks(被 9293 更新)。
RFC 6528 / 6093 / 6429 / 879 / 2873 / 6691分别涉及 ISN 生成、紧急指针语义、零窗口探测、MSS、TCP 优先级、TCP MSS 的 IPv6 说明——全部被 9293 吸收废止。
Misra, Gong, Towsley (SIGCOMM 2000)TCP 流体模型:用耦合 ODE 描述 cwnd 与队列演化(dW/dt、dq/dt),是 §12 排队方程的来源。
Zhang, Ren, Lin (INFOCOM 2011)数据中心 TCP(DCTCP)incast 分析:同步 fan-in 下微突发的闭式刻画,§12.4 的 ZRL 模型来源。
Shan, Jiang, Ren (INFOCOM 2015)共享缓存阈值建模:动态/静态阈值下 buffer 占用与丢弃的闭式条件,对应 §12.4 交换机 microburst。