零窗口探测是接收窗口为 0 时,发送方仍要发出的探测,用来得知窗口有没有重新打开。现行文本是 2022 年 8 月的 RFC 9293(STD 7)。截至 2026 年 10 月,探测必须被支持。第一次应当等过当前重传超时,此后间隔应当指数加长。这不是 TCP 保活。
带编号的必须只有「要支持探测」
一个可独立引用的定义句:零窗口探测是:即使发送窗口为 0,发送方也要定期向接收方发出至少一字节新数据(若有),或者重传,以便窗口重新打开时能被可靠地报告。其他文件把它称作 ZWP。探测零窗口必须被支持(RFC 9293 第 3.8.6.1 节,MUST-36)。
第 2.1 节写明,BCP 14 的关键词只在全大写时生效。上面「至少一字节」那句用的是小写 must,没有编号。带编号的必须是 MUST-36。本文不把「至少一字节」升级成另一条必须。
| 编号 | 要求 | 力度 |
|---|---|---|
| MUST-36 | 零窗口探测必须被支持 | 必须 |
| SHLD-29 | 第一次探测应当发生在零窗口已经持续了一个重传超时之后 | 应当 |
| SHLD-30 | 此后各次探测的间隔应当指数加长 | 应当 |
| MAY-8 | 接收方可以把提供的窗口一直关着 | 可以 |
| MUST-37 | 只要接收方继续确认探测,发送方就必须让连接保持打开 | 必须 |
| MUST-35 | 窗口若收缩到 0,必须按这一节的办法探测 | 必须 |
SHLD-30 只说指数加长,没有写乘以 2,也没有写 60 秒上限。那两项属于重传超时到期之后的规则,见 TCP 重传超时。第一次等待用的是当时的重传超时,超时本身按 RFC 6298 计算(第 3.8.1 节,MUST-18)。不要把「加倍」抄到这只探测间隔上。
接收方可以一直不打开窗口。发送方必须保持连接的前提,是探测仍被确认。原文把「打印机缺纸」的例子指到 RFC 1122 第 4.2.2.17 节。RFC 9293 的页首写明,它应被视为取代 RFC 1122 里关于 TCP 要求的那些部分。本文不把 1122 的旧措辞再当成现行要求。
同一段还说,这一行为受实现的资源管理考虑约束,并指向 RFC 6429。RFC 9293 的页首同时写明取代 RFC 6429。本文不把已经被取代的文件再写成现行条款,只保留「受资源管理考虑约束」这句。某一系统会在确认仍在到达时拆掉连接,本文没有测量。
带数据的探测段不会被收进窗口
一个可独立引用的定义句:接收窗口为 0 且段长度大于 0 时,该段不在可接受之列。窗口和段长度都为 0 时,只有序号等于下一个期望序号才可接受(RFC 9293 第 3.4 节,表 5)。
| 段长度 | 接收窗口 | 是否可接受 |
|---|---|---|
| 0 | 0 | 仅当序号等于下一个期望序号 |
| 0 | 大于 0 | 序号落在窗口内 |
| 大于 0 | 0 | 不可接受 |
| 大于 0 | 大于 0 | 段首或段尾落在窗口内 |
因此,带着至少一字节数据的探测,在窗口仍为 0 时不会被收进数据流。第 3.8.6.1 节另有一句小写 must:窗口为 0 时若有段到达,接收方仍要回一个确认,写明下一个期望序号和当前窗口(0)。这句同样没有编号。确认不把窗口左沿向前推进。窗口后来打开,才是另一份确认的事。
第 3.4 节还有一条带编号的必须:即使接收窗口为 0,接收方也必须处理收到的每一段里的 RST 和 URG 字段(MUST-66)。窗口为 0 不等于这两项被跳过。
窗口字段如何被位移放大,见 TCP 窗口缩放。字段本身是 0 时,左移之后仍是 0。缩放不把零窗口变成非零窗口。
接收方不应当把窗口右沿向左收(SHLD-14),但发送方必须能应付窗口被收小,包括可用窗口变成负数(MUST-34)。收小但还没到 0 时,不应当发送新数据(SHLD-15),应当照常重传仍在窗口内的旧数据(SHLD-16)。收到 0,才是 MUST-35 的探测。傻窗口综合征写在下一小节,管的是窗口一次只挪一小截。它和零窗口不是同一条规则。Nagle 与延迟确认见 Nagle 与延迟确认。
第 3.8.6.2.1 节说,傻窗口回避所用的超时可以方便地和零窗口探测的定时器合在一起。这句用的是小写 may。合不合在一起,不是必须。
它不是保活
一个可独立引用的定义句:TCP 保活是实现可以选择加上的机制,默认必须关闭。它只在没有未确认数据、并且一个间隔内没有收到数据或确认时发送。该间隔必须可配置,默认不得小于两小时(RFC 9293 第 3.8.4 节,MAY-5、MUST-25、MUST-26、MUST-28)。
| 零窗口探测 | TCP 保活 | |
|---|---|---|
| 何时出现 | 窗口已经为 0 | 连接空闲,没有未确认数据 |
| 是否必须实现 | 必须支持探测 | 可以不实现;实现了也必须默认关闭 |
| 第一次等多久 | 应当等过当前重传超时 | 默认不得小于两小时 |
| 段里有什么 | 至少一字节新数据(若有),或者重传 | 应当不带数据;也可以配置成带一字节无意义数据 |
| 序号 | 原文没有写成「下一个发送序号减 1」 | 一般是 SND.NXT 减 1 |
第 3.8.4 节写明:不含数据的确认不会被 TCP 可靠地传送。保活若被实现,不得把「某一次探测没有回应」直接当成对端已死(MUST-29)。零窗口探测要解决的是另一件事:窗口重新打开的消息,不能只靠一份可能丢失的纯确认。
客户端界面上的心跳、失效检测和自动重连,见 VPN 保活与掉线重连。那些间隔不改写 RFC 9293 的这两只钟。
2026 年 10 月 18 日查阅 RFC 9293 勘误。已验证的条目改的是第 3.10.7.3 节对 RST 的一处叙述、附录 B 里 SHLD-25 的列,以及图 5 的标注。标为 Held for Document Update 的条目在第 3.10.7.4 节。没有看到改写第 3.8.6.1 节的条目。被拒绝的勘误本文不采用。若该页当时没有展开全部记录,以编辑部页面为准。
外层换了传输,内层的窗口还在
外层若是 UDP,这一层没有接收窗口,也没有零窗口探测。外层若是 TCP,隧道自己的连接另有自己的窗口。封装不把内层的 0 抄到外层,也不把外层的探测间隔抄进内层。外层用哪一种传输,见 VPN 用 TCP 还是 UDP。
内层对端若把窗口通告为 0,内层仍须支持探测。外层若是 TCP,外层仍在转发只说明外层窗口没有堵住,不说明内层应用已经把数据读走。
一个可独立引用的结论句:零窗口探测是接收窗口为 0 时的发送方行为。它不随隧道图标消失,也不会因为外层换成 UDP 就从内层 TCP 上拿掉。
本文不提供修改探测间隔、关闭保活或调整重传超时的步骤。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
| 窗口字段为 0 之后,仍偶尔出现一字节数据段 | 零窗口探测。数据在窗口仍为 0 时不被接受 |
| 这些探测一次比一次隔得更久 | SHLD-30。原文没有写明乘以几 |
| 第一次探测大约等于当时的重传超时 | SHLD-29。超时本身按 RFC 6298 计算 |
| 空闲两小时量级才出现序号减 1 的段 | 更像第 3.8.4 节的保活,而且实现必须默认关闭保活 |
| 窗口字段很小但不是 0 | 不是这条探测。先看缩放和傻窗口回避 |
| 外层是 UDP,下载仍会停住再续 | 内层自己的窗口或重传超时。外层没有接收窗口 |
核对清单
- 探测必须被支持:第一次应当等过当前重传超时,此后应当指数加长。倍数没有写在这一节;
- 窗口为 0 时,带数据的段不可接受:接收方仍要回确认,序号不因此前进。小写 must 没有编号;
- 确认还在,连接就必须保持打开:接收方可以一直关着窗口。资源上限不要写成 RFC 6429 的现行条款;
- 保活是另一只钟:默认关闭,间隔默认不得小于两小时,不得因一次无回应就拆连接;
- 外层 UDP 没有这只探测:内层 TCP 的窗口仍由对端通告。缩放不把字段 0 变成非零。