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 等伴随文档规定。
一条 TCP 连接由四元组唯一标识:(源IP, 源端口, 目的IP, 目的端口)。同一台服务器可以用同一个 443 端口同时服务成千上万的连接,因为每个连接的源 IP/源端口组合不同。TCP 报文段封装在 IP 数据报中传输:[ IP 首部 | TCP 首部 | 应用数据 ]。下文第 2 节将逐字段剖析 TCP 首部。
TCP 首部固定部分 20 字节,加上可变长选项后最长 60 字节,其后才是应用数据。下图为交互式首部结构图(与 RFC 9293 图 1 一致):把鼠标悬停在任意字段上即可在下方说明栏查看该字段的详细解释;单击字段可以「钉住」当前说明,方便滚动阅读,再次单击取消。
| 字段 | 位数 | 要点 |
|---|---|---|
| Source Port | 16 | 发送方端口;四元组的一部分。 |
| Destination Port | 16 | 接收方端口;决定交付给对端哪个服务。 |
| Sequence Number | 32 | 本报文段第一个数据字节的序号;SYN=1 时为 ISN,首字节为 ISN+1。 |
| Acknowledgment Number | 32 | ACK=1 时有效;期望收到的下一字节序号,即累计确认。 |
| Data Offset | 4 | 首部长度,单位 32 位字;5 = 20 字节,最大 15 = 60 字节。 |
| Reserved | 4 | 置 0 发送、收到忽略(RFC 793 为 6 位,ECN 借走 2 位)。 |
| CWR / ECE | 1+1 | 显式拥塞通知 ECN(RFC 3168):拥塞窗口已减小 / ECN 回显。 |
| URG / ACK / PSH | 1×3 | 紧急指针有效 / 确认号有效 / 立即推送。 |
| RST / SYN / FIN | 1×3 | 复位连接 / 同步序号(建连)/ 发送完毕(断连)。 |
| Window | 16 | 接收窗口 rwnd,流量控制;无符号数,可被窗口扩大选项左移。 |
| Checksum | 16 | 伪首部+首部+数据的反码和取反;强制生成、强制校验。 |
| Urgent Pointer | 16 | URG=1 时有效;紧急数据末尾的序号偏移。 |
| Options | 0–320 | DataOffset>5 时存在;EOL/NOP/MSS 必须支持。 |
| Data | 可变 | 应用数据;长度受 MSS、MTU、窗口约束。 |
计算校验和时,TCP 在首部之前概念性地拼上一个 96 位(IPv4)伪首部——它来自 IP 层,并不随报文传输。把源/目的地址纳入校验,可以防止报文段被错误路由后仍被接收方接受。IPv4 伪首部结构如下(悬停查看各字段):
选项位于首部末尾,总长须为 4 字节的整数倍(不足时以 EOL/零填充)。RFC 9293 规定所有实现必须支持 EOL、NOP、MSS 三个选项(MUST-4);窗口扩大、SACK、时间戳定义于 RFC 7323 / 2018 等文档,是实现高性能 TCP 的事实标准。悬停下列选项卡片查看详情。
| 选项 | Kind | 长度 | 用途与要点(悬停行查看详情) |
|---|---|---|---|
EOL | 0 | 1 字节 | 标记选项列表结束。 |
NOP | 1 | 1 字节 | 填充对齐用,无实际语义。 |
MSS | 2 | 4 字节 | 声明本端最大接收段长,仅 SYN 中出现。 |
WSOPT | 3 | 3 字节 | 窗口左移 0–14 位,突破 64 KiB 上限。 |
SACK-Perm | 4 | 2 字节 | 握手中声明支持选择确认。 |
SACK | 5 | 可变 | 报告已收到的非连续块,精确重传。 |
TSopt | 8 | 10 字节 | RTT 测量 + 防序号回绕(PAWS)。 |
建立连接的本质是双方可靠地交换初始序列号(ISN)并确认对方收到。下图为标准的三次握手时序,悬停每个报文查看该步的细节与状态变迁。
tcp.seq_raw 总是一大串随机数的原因(§11.3)。FIN 的含义是「我这一方数据发完了」。由于 TCP 全双工,两个方向要分别关闭,因此正常释放需要四个报文。任一方向关闭后另一方向仍可传输,称为半关闭(half-closed,§3.6.1)。悬停各报文查看细节。
教科书是 4 个报文,真实抓包常常「不像」,因为 TCP 会合并报文(coalescing):
close(),内核把 [PSH, ACK, FIN] 合成一个包,看不到独立的 FIN 段;[FIN, ACK] 一个包——HTTP 短连接里极常见;判断异常要看 RST、重传、零窗口,而不是「段数不够 4」。但注意:关闭不可能只有 2 段——少任何一个方向的 FIN 或它的 ACK 都关不完,TCP 没有「快速挥手」机制。
FIN-WAIT-2(FIN 已被 ACK,对方的 FIN 迟迟不来):多数内核有超时兜底(Linux tcp_fin_timeout,默认 60 秒),孤魂套接字会被自清;CLOSE-WAIT:没有任何内核超时,只能等应用调 close()。应用漏调 close、连接池不释放、keep-alive 卡死 → CLOSE-WAIT 只涨不跌,文件描述符耗尽,服务假死;一条连接在任一时刻处于 11 种状态之一,状态随「用户调用 / 报文到达 / 超时」三类事件迁移。下图为完整状态迁移图:实线为正常路径,虚线为较少见路径;悬停任一状态节点查看其含义,迁移条件标注在箭头上(recv = 收到,send = 发送)。
| 当前状态 | 触发事件 | 动作 | 下一状态 |
|---|---|---|---|
| CLOSED | 主动 OPEN | 发送 SYN | SYN-SENT |
| CLOSED | 被动 OPEN(listen) | — | LISTEN |
| LISTEN | 收到 SYN | 发送 SYN+ACK | SYN-RECEIVED |
| LISTEN | CLOSE | — | CLOSED |
| SYN-SENT | 收到 SYN+ACK | 发送 ACK | ESTABLISHED |
| SYN-SENT | 收到 SYN(同时打开) | 发送 SYN+ACK | SYN-RECEIVED |
| SYN-RECEIVED | 收到 ACK | — | ESTABLISHED |
| SYN-RECEIVED | CLOSE(少见) | 发送 FIN | FIN-WAIT-1 |
| ESTABLISHED | CLOSE | 发送 FIN | FIN-WAIT-1 |
| ESTABLISHED | 收到 FIN | 发送 ACK | CLOSE-WAIT |
| FIN-WAIT-1 | 收到 ACK | — | FIN-WAIT-2 |
| FIN-WAIT-1 | 收到 FIN(同时关闭) | 发送 ACK | CLOSING |
| FIN-WAIT-1 | 收到 FIN+ACK | 发送 ACK | TIME-WAIT |
| FIN-WAIT-2 | 收到 FIN | 发送 ACK | TIME-WAIT |
| CLOSING | 收到 ACK | — | TIME-WAIT |
| CLOSE-WAIT | CLOSE | 发送 FIN | LAST-ACK |
| LAST-ACK | 收到 ACK | — | CLOSED |
| TIME-WAIT | 2MSL 超时 | — | CLOSED |
注:除 RST 触发的迁移外,表中列出正常迁移;任何状态收到合法 RST 都会中止连接回到 CLOSED/LISTEN。
何时生成 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 相对旧文档为数不多的实质改动之一。
TCP 在不可靠的 IP 之上提供可靠字节流,依赖四根支柱:编号与确认、超时重传、快速重传、差错检测。
0 ≤ (b − a) < 2³¹(模 2³²)时认为 a < b。高速链路上序号可能数秒内回绕一圈——一个迟到的旧段,其序号可能恰好「回绕」进当前窗口而被误当新数据。防护机制是时间戳选项的 PAWS(Protect Against Wrapped Sequences,RFC 7323):每个段携带单调递增的发送时间戳,接收方维护「最近有效时间戳」,凡是时间戳更老的段一律丢弃——于是旧连接的迟到段即使序号巧合落入窗口,也会因时间戳过时被剔除。Next Seq 不是首部字段,是它按 Seq + Len + SYN/FIN 各占 1 帮你算出的派生值(线上只传 Seq 和 Ack)。健康连接中恒有「接收方回的 Ack == 发送方的 Next Seq」——拿它逐包验算,丢没丢包一眼可见。完整示范见第 11 节。每发出一个含数据的段即启动重传定时器,超时(RTO)未收到确认则重传。RTO 不能拍脑袋定:太小导致不必要的重传加剧拥塞,太大则丢包恢复缓慢。标准做法是 Jacobson/Karels 算法,动态跟踪 RTT 的均值与波动:
RTO = SRTT + max(G, 4·RTTVAR),G 为系统计时器粒度(越细越好);拿到首个 RTT 样本前初始 RTO = 1 秒;建议上限 60 秒;采样规则——首个样本直接令 SRTT = R′、RTTVAR = R′/2,之后每个新样本才按上式以 α、β 平滑更新。接收方收到失序段时会立即重发对已收妥字节的确认(重复 ACK)。若发送方连续收到 3 个重复 ACK(dupthresh = 3),说明某个段大概率已丢失而非仅仅延迟——于是不等超时立即重传该段。快速重传把丢包恢复时延从一个 RTO 缩短到一个 RTT 量级,通常与快速恢复(§9)配合。
每个 ACK 报文中的 Window 字段都在告诉发送方:「从确认号开始,我还能再收 rwnd 个字节」。发送方据此维护发送窗口,任何时刻未确认数据量不得超过对方通告的窗口——这就是滑动窗口流量控制,它保证快速发送方不会淹没慢速接收方。悬停下图各区域查看含义。
TCP_NODELAY 关闭。min(拥塞窗口 cwnd, 接收窗口 rwnd)。rwnd 保护接收方,cwnd 保护网络,两者取小。RFC 9293 有意不收录完整的拥塞控制算法(§1:让基础规范可与多种算法组合),只要求实现必须支持某种拥塞控制,并指向伴随文档。以下是经典算法(RFC 5681「TCP 拥塞控制」+ RFC 6298「RTO 计算」)的框架,现代实现(CUBIC、BBR)在此基础上演进。
cwnd(拥塞窗口):发送方给自己设的在途数据上限(字节)。ssthresh(慢启动阈值):cwnd 低于它走慢启动,高于它走拥塞避免。rwnd:对方通告的接收窗口;实际上限取 min(cwnd, rwnd)。| 机制 | 工作方式 |
|---|---|
| 慢启动 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(「乘性减小」),直接进入拥塞避免而非慢启动——个别丢包不丢弃辛苦探测到的带宽估值。 |
net.ipv4.tcp_mtu_probing 兜底开关。TCP 是字节流协议,不保留应用层消息边界:一次 send 可能被拆成多个段,多次 send 也可能被 Nagle 合并进一个段。期望按「条」收消息是应用层的责任,常见做法:① 定长消息;② 分隔符(如 HTTP/1.x 的 \r\n\r\n);③ 长度前缀(如先发送 4 字节长度)。UDP 才是保留报文边界的协议——这也是「面向报文」与「面向字节流」的本质区别。
经验:服务器大量 CLOSE-WAIT → 应用忘记 close;大量 TIME-WAIT → 本端在主动关闭短连接,考虑长连接复用;大量 SYN-RECEIVED → 疑似 SYN 泛洪。Wireshark 侧的过滤器与专家信息读法见第 11 节。
前面讲的是规范怎么写,这一节看线上长什么样。内容来自对真实生产抓包(内网监控终端 ↔ 负载均衡,单条连接 66 个报文)的完整复盘,重点收录初学者最容易犯晕的几个点,与前面的规范条文一一对应。
全双工意味着两本账、四个数同时跑:客户端的 Seq 由服务端的 Ack 追踪,服务端的 Seq 由客户端的 Ack 追踪,互不干扰。看到 C→S 一行的 Ack 在变化,那是客户端在确认「服务端的数据收到哪了」,与这一行自己的 Seq 无关——别被它搞懵。
以下是一条健康连接(HTTP over TCP,Wireshark 相对序号视图)的三次握手与第一轮请求/响应。拿黄金等式逐行验算:每个「对方回的 Ack」都精确等于「你的 Next Seq」。
| # | 方向 | 标志 | Seq | Len | Next Seq | Ack | 速读(悬停看详解) |
|---|---|---|---|---|---|---|---|
| 46826 | C→S | SYN | 0 | 0 | 1 | 0 | 敲门,SYN 占 1 号 |
| 46827 | S→C | SYN,ACK | 0 | 0 | 1 | 1 | 应门,确认 ISN |
| 46828 | C→S | ACK | 1 | 0 | 1 | 1 | 闭环,账本对齐 |
| 46829 | C→S | ACK | 1 | 1388 | 1389 | 1 | 请求首段,顶满 MSS |
| 46830 | C→S | PSH,ACK | 1389 | 247 | 1636 | 1 | 尾段带 PSH,共 1635 B |
| 46831 | S→C | ACK | 1 | 0 | 1 | 1636 | 一个 Ack 顶两段 ✓ |
| 46832 | S→C | ACK | 1 | 1388 | 1389 | 1636 | 响应首段 |
| 46833 | S→C | PSH,ACK | 1389 | 16 | 1405 | 1636 | 响应中段 16 B |
| 46834 | S→C | PSH,ACK | 1405 | 5 | 1410 | 1636 | 5 B = chunked 终止块 |
| 46835 | C→S | ACK | 1636 | 0 | 1636 | 1389 | 确认首段 |
| 46836 | C→S | ACK | 1636 | 0 | 1636 | 1405 | 确认中段 |
| 46837 | C→S | ACK | 1636 | 0 | 1636 | 1410 | 闭环 ✓ 全收 |
这条连接按同样的流水线跑了 9 轮半(客户端共发 15008 B,服务端回 11402 B),全程零重传、零 RST、Ack 严格单调——教科书级健康。最后一包(#48594,又一个 1388 字节请求首段)没等到 ACK:不是丢了,是抓包在这一刻停了,服务端的确认没来得及被记录。看抓包下结论前,先看抓包窗口。
| 疑问 | 正解 |
|---|---|
| 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] 提示,别急着判异常。 |
① 显示过滤器(逻辑运算: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) 里,重传表现为「往回掉再跟上」的锯齿。
CLOSE_WAIT 等后端关闭——后端不发它自己的 FIN,LB 就没资格给客户端发 FIN,抓包便看到「只有一半的挥手」。LB 先 FIN 时通常能干净关完(说明后端已就绪)。close(),socket 泄漏,去查后端。回顾 §5.3:FIN-WAIT-2 有内核超时兜底,CLOSE-WAIT 没有。ss。tcpdump -s 0(snaplen 拉满);双层 VLAN(QinQ)链路上抓包需在 Wireshark 确认 VLAN 解析正常,否则内层 IP/TCP 显示不全。11.1–11.5 都默认抓包是干净完整的,真实世界常常不是:镜像口把同一份包交错复制了好几遍、tcpdump 默认 snaplen 只留 64 字节、链路还有乱序。这一节处理三件事——抓包本身可不可信、窗口归零该查谁、按什么顺序看包。要点来自一次带重复镜像包与 64 字节截断的真实抓包复盘。
① 先给抓包「体检」:文件质量决定判读可信度
| 文件问题 | 判读时的折扣 | 结论 |
|---|---|---|
| 重复镜像包 | 同一份包交错重复多遍,包数虚高 | 去重后可看连接/方向,但补不回漏抓 |
| snaplen 截断(64 B 常见) | 只留头部、载荷残缺 → Len 值失真 | Len≠应用真实长度;重组/专家分析可信度下降 |
| 乱序 / 丢抓 | 序号推断与重组失真 | 别把分析提示直接当线上故障 |
② 红色提示的正确读法:分析提示 ≠ 线上字段
方括号里的 [TCP …] 是 Wireshark 依序号关系推算出来的注解,不是 TCP 首部里真实携带的字段。在脏抓包里尤其要降级看待:
| 提示 | 真实含义 |
|---|---|
ACKed unseen segment | ACK 确认到了抓包未见的数据——多为抓包起点/漏看问题 |
Previous segment not captured | 前面的数据段没抓到——FIN 带此提示也正常 |
Malformed Packet / Bogus IP length | 解析器失去上下文所致,脏抓包里不一定是真故障 |
③ Zero Window ↔ Window Update:先分清是谁喊的
Win=0 是接收方在喊「我满了」:报文里的 Window 通告的是发送该报文这一端的剩余接收空间。看到 A → B:Win=0,含义是 A 暂时收不下 B 的数据、B 应暂停正常发送——瓶颈在 A(接收方),优先查它的应用读取速度与接收缓存,不是发送方故障。Ack=500 Win=0 → Ack=500 Win=4096)——这就是 Window Update(§8.1)。④ 到 Linux 上印证:ss -tinp
判断方向看谁发出了 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 的红色专家提示(先排除抓包质量,再谈故障) |
§8 讲了窗口怎么滑、§9 讲了 cwnd 怎么涨。这节把「窗口」和「实际能发多快」彻底串起来,并延伸到数据中心里绕不开的微突发(microburst)——它正是交换机 buffer、ECN、PFC/ETS 调优要压的场景。
§8.1 已给出在途上限 min(cwnd, rwnd)。把接收窗口 rwnd 记作 W(不失一般性,取瓶颈窗口),则任意时刻「已发未确认(在途)」的字节数 ≤ W。于是 TCP 会连续发送数据,直到在途量(FlightSize)达到发送窗口 W,窗口就满了,必须等 ACK 回来才能发下一个 W——这就是「窗口受限」。
ACK 为 TCP 提供发送时钟(ACK Clock),窗口 W 决定每个 RTT 能发送的数据量,因此持续发送速率为 W/RTT。 一个 RTT 内,发送方随 ACK 的节奏连续注入数据、累计正好一个窗口 W,所以平均持续速率 = 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)。窗口推导出的可持续发送速率与链路线速是两个上界,二者取小:
| 量 | 含义 / 数据中心典型值 |
|---|---|
BDP = C × RTT | 管道里「在途」的数据量。100G 线速、RTT≈10µs → BDP≈122 KB(极小)。 |
W(发送窗口) | 常见 MB 级(窗口扩大选项)。故 W ≫ BDP,窗口足够大,真正限制吞吐的是链路线速,而非发送窗口。 |
W / RTT | 窗口推导出的可持续发送速率,在 DC 中 ≫ 线速 C。 |
| 实际吞吐 | = C(被线速夹住);但聚合到达 N·W/R 远超 C,这正是微突发的根源。 |
微突发:许多条流在同一时刻把流量怼进同一个出口,队列在微秒级暴涨、超过交换机 buffer,来不及被 TCP 降窗反应就丢包。数据中心低 RTT 非但没「治好」它,反而喂肥了它:
用排队方程一句话说清(流体模型,Misra–Gong–Towsley SIGCOMM'00 框架):
闭式 incast 模型(Zhang–Ren–Lin, INFOCOM'11,逐 RTT 同步分析)给出单流排队上限:
qos burst-mode、ECN 阈值要压的场景。回到 §8(窗口滑动)与 §9(cwnd):微突发本质是「聚合到达 N·W/R 压垮单出口 C」,而 §9 的加性增大/乘性减小(AIMD)降窗是这个压力的唯一反馈回路——可惜在 DC 微秒级突发里它慢了整整一个 RTT。
| 结论 | 一句话 |
|---|---|
| 窗口受限 | 在途 ≤ 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]。 |
| 术语 | 含义 |
|---|---|
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 标志与确认号,节省报文。 |
| 编号 | 内容 |
|---|---|
| RFC 9293 | Transmission Control Protocol (TCP),STD 7,2022-08。本文的主要依据。 |
| RFC 793 | TCP 原始规范(1981),已被 RFC 9293 废止。 |
| RFC 1122 | 互联网主机要求——通信层;延迟 ACK、SWS 对策、Keep-Alive 等(被 9293 更新吸收)。 |
| RFC 5681 | TCP Congestion Control:慢启动、拥塞避免、快速重传、快速恢复。 |
| RFC 6298 | Computing TCP's Retransmission Timer:RTO 算法(SRTT/RTTVAR/Karn)。 |
| RFC 7323 | TCP Extensions for High Performance:窗口扩大、时间戳、PAWS。 |
| RFC 2018 / 2883 | TCP Selective Acknowledgment Options / SACK 扩展(D-SACK)。 |
| RFC 3168 | The Addition of Explicit Congestion Notification (ECN) to IP:CWR/ECE 标志位。 |
| RFC 5961 | Improving 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。 |