<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>HTTP/3 · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/http3/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/http3/index.xml" rel="self" type="application/rss+xml"/><item><title>HTTP3 走 UDP，HTTP2 仍走 TCP 兜底</title><link>https://hk.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</link><pubDate>Sat, 12 Feb 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</guid><description>HTTP/3 使用基于 UDP 的 QUIC 减少跨流传输阻塞；双方支持 HTTP/2 时，它仍可作为 TCP 上的回退选择。</description><content:encoded>&lt;p&gt;HTTP/3 运行在 QUIC 与 UDP 之上，常用端口是 443，但协议不要求只能使用这个端口。HTTP/2 各流共用 TCP 的有序字节流，缺失片段可能阻塞无关请求的数据交付。QUIC 按流提供有序交付，某条流丢失数据不必阻塞另一条流；连接级拥塞控制与 HTTP 层依赖仍可能影响多个流，不能说每条流都有完全独立的拥塞控制或丢包恢复状态。&lt;/p&gt;
&lt;p&gt;仅启用服务器模块还不够。客户端需要 HTTP/3 支持、可代表该源的端点、可验证的 TLS 身份，以及实际可用的 UDP 路径。发现机制和回退策略决定何时尝试它；不能把此前必须成功访问一次 TCP 服务当成所有客户端都要满足的协议前提。&lt;/p&gt;
&lt;h2 id="两种传输一个端口号"&gt;两种传输，一个端口号&lt;/h2&gt;
&lt;p&gt;UDP:443 与 TCP:443 使用不同套接字，只是端口数字相同；并不是一个套接字同时监听两种传输。nginx 的 server 块可以并列配置 &lt;code&gt;listen 443 quic reuseport;&lt;/code&gt; 与 &lt;code&gt;listen 443 ssl; http2 on;&lt;/code&gt;。受支持系统上的 &lt;code&gt;reuseport&lt;/code&gt; 用于每个 worker 的监听套接字，不是修复重复 server 声明或其他进程占用端口的万能选项。应先核对完整配置、实际错误与套接字归属，再判断是否需要修改。&lt;/p&gt;
&lt;p&gt;QUIC 将 TLS 1.3 与传输建立结合，能够减少建立连接的往返次数。恢复会话时可能发送早期应用数据，但握手并未消失，服务器也不一定接受 0-RTT。Retry、丢包与验证都可能增加等待。连接 ID 可以在端点与路由设施支持时帮助连接跨地址变化延续；它本身不承诺所有漫游都成功，也不会自动修复 UDP 不通的路径。&lt;/p&gt;
&lt;h2 id="alt-svc-是客户端得知的方式"&gt;Alt-Svc 是客户端得知的方式&lt;/h2&gt;
&lt;p&gt;源站可以通过已有 HTTP 连接发布 &lt;code&gt;Alt-Svc: h3=&amp;quot;:443&amp;quot;; ma=86400&lt;/code&gt;，表示协议 h3、同一主机的 443 端口，以及二十四小时的新鲜期。该连接不必是 HTTP/2；已知目标支持 HTTP/3 的客户端也可以直接尝试，不需要先访问一次 TCP 服务。客户端可以使用仍新鲜的替代服务，不必等待下次页面访问。收到通告只证明服务器发出了信息，不能证明该客户端的 UDP 路径真正可用。&lt;/p&gt;
&lt;p&gt;新鲜期允许复用通告，不会强迫客户端每次新建连接都重复一个已经失败的 QUIC 尝试。客户端可以回退，也可以使用自己的失败处理策略。QUIC 探测定时器取决于往返时间估计及退避，而何时并行竞速、放弃尝试属于实现选择。早先所写的十五秒、每天三百秒和零点一秒是没有保留可运行模型或原始轨迹的示意假设，既不是实测，也不是协议规定的回退时间，不能据此估算真实用户损失。&lt;/p&gt;
&lt;h2 id="0-rtt-与连接迁移是两处锋利的边缘"&gt;0-RTT 与连接迁移是两处锋利的边缘&lt;/h2&gt;
&lt;p&gt;0-RTT 允许回访客户端在新握手完成前发送应用数据。早期数据存在重放风险，因此应用必须明确哪些处理能安全重放，并拒绝不安全的早期操作；持有旧票据也不保证服务器接受。未经保护的创建订单请求可能重复产生副作用，传输加密并不提供恰好一次执行保证。判断能否使用早期数据，应依据业务语义与服务端保护，不能只看请求是否已经加密。&lt;/p&gt;
&lt;p&gt;客户端地址改变后，迁移可以保留 QUIC 连接，但受到对端策略与路径验证约束。按五元组选择后端的均衡器可能把报文发给没有该连接加密状态的另一台后端，造成中断或重置，而不是让它自然接管原连接。早先从七号落到四号的示例没有保留哈希模型或原始轨迹，不能当成测得的落点，也不能据此断言每次地址变化一定选中不同后端。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;缓存通告与 UDP 阻断可能拖慢连接建立，但不能保证整整一天每次访问都慢，也不能仅凭 HTTP/3 失败断定中间盒故障。客户端缺少支持、TLS 错误、端点配置或服务器不可用，都可能导致失败。应对照客户端错误、分协议延迟与服务端观察；访问日志里没有请求并不足以定位丢包。保留可工作的 TCP 备用路径，并测量实际客户端的回退策略，才能判断等待发生在哪里。&lt;/p&gt;
&lt;p&gt;地址变化还可能暴露错误的路由亲和性。应保持报文到达拥有该连接状态的 QUIC 端点；仅靠应用 Cookie，无法在 QUIC 终止之前为加密报文选择后端。需要显式验证部署中的连接 ID 处理与路径变化，也不能假定连接 ID 永远不变。此类问题不限于移动客户端；传输恢复成功之后，应用会话是否连续仍是另一层问题，不能把两种状态混为一个保证。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-nginx-http3-http2-fallback.detail.zh.svg" alt="独立 UDP 与 TCP 监听格接到已有 HTTP 通告及预先获知两条发现分支；共享拥塞面板区分缺失数据流与仍可能交付的流，随后说明早期数据、迁移及诊断限制。" width="560" height="2357"&gt;
&lt;figcaption&gt;可直接发现 h3；h2 回退取决于双方支持及客户端策略。不保证固定回退耗时或迁移成功。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;使用 Bash、Python 3，以及编译时支持 HTTP/2 和 HTTP/3 的 curl 7.88 或更新版本。把 URL 设置为获准直接访问、GET 预期返回 200 的完整 HTTPS 地址，包含路径；这些请求不使用重定向或代理。目标应是允许重复读取的诊断资源，而不是有副作用的操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;: &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;:?Set one authorized HTTPS URL with an expected 200 response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;077&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rm -rf -- &amp;#34;$tmp&amp;#34;&amp;#39;&lt;/span&gt; EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;probe&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="nv"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt; rc check_rc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; curl --disable --silent --show-error --fail --globoff &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --noproxy &lt;span class="s1"&gt;&amp;#39;*&amp;#39;&lt;/span&gt; --proto &lt;span class="s1"&gt;&amp;#39;=https&amp;#39;&lt;/span&gt; --connect-timeout &lt;span class="m"&gt;10&lt;/span&gt; --max-time &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --output /dev/null --dump-header &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.headers&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --write-out &lt;span class="s1"&gt;&amp;#39;%{json}\n&amp;#39;&lt;/span&gt; --url &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%s: curl failed (exit %s)\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; python3 - &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;.json&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;PY&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;import json, math, pathlib, sys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;path, label, expected, url, curl_exit = sys.argv[1:]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;print(label, &amp;#34;curl_exit&amp;#34;, curl_exit)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;d, headers = None, None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; d = json.loads(pathlib.Path(path).read_text())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if not isinstance(d, dict):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; raise ValueError(&amp;#34;expected a JSON object&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;except (OSError, UnicodeError, ValueError) as error:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; d = None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(label, &amp;#34;metadata unavailable:&amp;#34;, str(error), file=sys.stderr)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;keys = (&amp;#34;url_effective&amp;#34;, &amp;#34;remote_ip&amp;#34;, &amp;#34;remote_port&amp;#34;, &amp;#34;http_version&amp;#34;, &amp;#34;http_code&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;ssl_verify_result&amp;#34;, &amp;#34;time_appconnect&amp;#34;, &amp;#34;time_starttransfer&amp;#34;, &amp;#34;time_total&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;if d is not None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(label, json.dumps({k: d[k] for k in keys + (&amp;#34;exitcode&amp;#34;, &amp;#34;errormsg&amp;#34;) if k in d}))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; missing = [k for k in keys if k not in d]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if missing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(label, &amp;#34;metadata fields unavailable:&amp;#34;, &amp;#34;, &amp;#34;.join(missing), file=sys.stderr)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; headers = pathlib.Path(path).with_suffix(&amp;#34;.headers&amp;#34;).read_text(encoding=&amp;#34;iso-8859-1&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(label, &amp;#34;Alt-Svc header observations:&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; [line for line in headers.splitlines() if line.lower().startswith(&amp;#34;alt-svc:&amp;#34;)])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;except OSError as error:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(label, &amp;#34;header observations unavailable:&amp;#34;, str(error), file=sys.stderr)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;if int(curl_exit) != 0:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # Observations from a failed transfer are not successful validation.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; raise SystemExit(0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ok = (d is not None and headers is not None and d[&amp;#34;http_code&amp;#34;] == 200 and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; d[&amp;#34;url_effective&amp;#34;] == url and d[&amp;#34;http_version&amp;#34;] in (&amp;#34;1.1&amp;#34;, &amp;#34;2&amp;#34;, &amp;#34;3&amp;#34;) and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; d[&amp;#34;remote_ip&amp;#34;] and d[&amp;#34;remote_port&amp;#34;] &amp;gt; 0 and d[&amp;#34;ssl_verify_result&amp;#34;] == 0 and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; (expected == &amp;#34;any&amp;#34; or d[&amp;#34;http_version&amp;#34;] == expected) and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; all(math.isfinite(d[k]) and d[k] &amp;gt;= 0 for k in
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; (&amp;#34;time_appconnect&amp;#34;, &amp;#34;time_starttransfer&amp;#34;, &amp;#34;time_total&amp;#34;)))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;except (KeyError, TypeError, ValueError):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ok = False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;if not ok:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; raise SystemExit(label + &amp;#34;: unavailable or unexpected endpoint, protocol, status or timing&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;PY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;check_rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;check_rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$check_rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;probe h2 --http2 &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;probe h3-only --http3-only &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;probe fallback-enabled --http3 any &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$failed&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个成功探测都检查状态码、URL 与实际协议。失败传输仍输出已有的协议、状态、对端、计时与响应头观察，并保留原始 curl 退出码和错误输出；缺失或无法解析的元数据会明确标为不可用。失败观察不算验证成功，即使 h3-only 失败，也继续执行后续探测，最终汇总仍为非零。&amp;ndash;http3-only 禁用回退，&amp;ndash;http3 则允许与较旧协议竞速。结果为 2 或 1.1 只记录选择了 TCP，不代表完成可控 UDP 阻断实验；1.1 也不能证明 h2。time_appconnect 从开始计到握手完成，time_starttransfer 从开始计到收到首字节；两者不是同一指标。Alt-Svc 输出只是原始响应头观察，可能包含中间响应，不证明通告有效或路径可达。&lt;/p&gt;
&lt;p&gt;在对应的获准服务器上，单独检查配置和本地套接字；不能把客户端的远端 IP 自动当成这台主机的身份：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;077&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rm -rf -- &amp;#34;$tmp&amp;#34;&amp;#39;&lt;/span&gt; EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; nginx -T &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nginx.txt&amp;#34;&lt;/span&gt; 2&amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nginx.err&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nginx.err&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nginx.err&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; grep -nE &lt;span class="s1"&gt;&amp;#39;listen .*quic|http2|http3|Alt-Svc&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nginx.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;No matching directives, or grep failed (exit %s)\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rc&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -H -lunp &lt;span class="s1"&gt;&amp;#39;sport = :443&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/udp.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/udp.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/udp.txt&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;No local UDP:443 listener observed\n&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;错误仍然可见。配置匹配可能包括注释或未加载的设置，套接字进程信息也可能受权限限制。没有进一步对应运行进程、部署配置与客户端端点之前，两者都不证明公网服务身份或可达性。配置输出可能敏感，审阅时应妥善保管。本轮只有 shell 语法与假命令／文件控制，没有执行真实 TLS、HTTP、NGINX 或回退验证，技术与运行检查仍暂缓。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9114.html"&gt;RFC 9114 HTTP/3&lt;/a&gt;，HTTP/3 在 QUIC 之上的语义，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7838.html"&gt;RFC 7838 HTTP 替代服务&lt;/a&gt;，Alt-Svc 头及其新鲜期，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_v3_module.html"&gt;ngx_http_v3 模块&lt;/a&gt;，nginx 的 QUIC 与 HTTP/3 指令，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9000.html"&gt;RFC 9000 QUIC&lt;/a&gt;，按流交付、连接 ID、迁移与 0-RTT，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_core_module.html"&gt;nginx core listen directive&lt;/a&gt;，独立监听及每个 worker 的 reuseport 语义，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，HTTP/3-only 模式、回退选择、错误与握手计时，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9002.html"&gt;RFC 9002 QUIC recovery&lt;/a&gt;，依赖 RTT 的探测定时器与指数退避，已核验 2026-10-18。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HTTP/3 分离流的交付顺序，不隔离全部恢复机制</title><link>https://hk.xbcatm.com/zh/posts/http3-and-quic-recover-per-stream/</link><pubDate>Sun, 21 Mar 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/http3-and-quic-recover-per-stream/</guid><description>QUIC 解除跨流交付顺序约束，但包恢复、拥塞、流控与应用依赖仍有共享边界。</description><content:encoded>&lt;p&gt;HTTP/3 把 HTTP 放在 QUIC 上传输，不再依赖 TCP。QUIC 提供逐流有序交付：一条流缺少某段内容时，另一条流已经完整收到的数据不一定必须跟着等待。它解除的是特定的传输顺序依赖，并没有给每个请求配备独立的拥塞控制器、丢包检测器或确定的完成期限。先区分交付顺序和恢复机制，才能准确理解收益。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-http3-and-quic-recover-per-stream.detail.zh.svg" alt="四个抽象块依次携带 A:a、缺失的 A:b、B:x、B:y。共享有序传输只交付 A=a，B 为空；逐流排序可交付 A=a 与 B=xy。混合丢失能影响两条流，B:y 也不能跳过缺失的 B:x。补齐后两种模型完整；包编号空间、共享路径、连接额度和 QPACK 或应用等待仍然存在。" width="720" height="2180"&gt;&lt;figcaption&gt;QUIC 解除一种跨流交付顺序依赖：A 缺字节时，完整的 B 仍可交付。但恢复、带宽、流控与应用依赖并未全部隔离；此排序模型不提供耗时结论。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="交付顺序不等于丢包检测"&gt;交付顺序不等于丢包检测&lt;/h2&gt;
&lt;p&gt;HTTP/2 的帧共享 TCP 的一个有序字节流。中间缺少一段时，后续字节即使属于另一个响应，也要等缺口补齐才能交给 HTTP。已经交付的内容不会被收回，应用也不会因为出现一次丢包就全部停止。究竟影响哪些请求，要看它们的字节相对缺口处于什么位置，不能笼统说整条连接上的所有工作都同时冻结。&lt;/p&gt;
&lt;p&gt;QUIC 在每条流内维护偏移量。当 A 流仍缺少前面的字节时，B 流完整收到的数据可以交付。不过，一个 QUIC 包可以携带多条流的帧，因此丢掉一个包也可能同时影响多条流。需要恢复的信息会装进新包再次发送；包编号与流偏移量承担不同职责，并不是每个流各自重新传送一份独立的旧包。&lt;/p&gt;
&lt;p&gt;RFC 9002 按包编号空间区分丢包检测，而不是按 HTTP 请求或流划分。往返时间估计与拥塞控制面向网络路径。逐流流控之外还存在连接级额度，因此共享容量不足或连接额度耗尽，仍会同时拖慢多个流。不能把独立排序扩写成独立带宽，也不能说某条流发生问题时邻居在任何条件下都不受影响。&lt;/p&gt;
&lt;h2 id="http-语义与部署边界仍然存在"&gt;HTTP 语义与部署边界仍然存在&lt;/h2&gt;
&lt;p&gt;方法、状态码和表示语义仍属于 HTTP。HTTP/3 改变消息分帧，并使用 QPACK 压缩字段。QUIC 集成 TLS 1.3 认证与包保护；调度和优先级策略仍要决定先发送哪些已经可用的流数据，并不会凭空增加带宽。如果某个字段区段依赖尚未到达的 QPACK 动态表项，它仍然可能等待。因此，去掉 TCP 的跨流顺序约束，并没有消灭协议内部或应用内部的全部依赖，共享数据库锁和服务端工作队列也不会随之消失。&lt;/p&gt;
&lt;p&gt;丢包路径可能受益，但应测量真实工作负载，而不是保证所有网页必然加速。处理器成本、握手成功率、调度、可用带宽和客户端实现都会影响结果。比较时要核对同一被测链路实际协商的协议及正文完成情况；边缘节点使用 HTTP/3，不能证明它连接源站时也采用同样协议，更不能凭协议名称推算生产收益。&lt;/p&gt;
&lt;p&gt;客户端可以通过 Alt-Svc 等机制发现 HTTP/3。UDP 连通性导致 QUIC 建立失败时，RFC 9114 建议尝试基于 TCP 的 HTTP，但这不是立即且必然回落 HTTP/2 的承诺。客户端策略与服务端实际开放的协议都重要。运营方仍需保留可达的替代路径、正确发布服务公告，并测量失败尝试和回退等待，不能把兼容性解释成部署无需管理。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;防火墙或中间设备可能阻断 QUIC 流量，也可能影响可用的数据报大小。成功发送服务公告，不代表客户端已经建立 QUIC。应按网络和协议记录失败，并在依赖回退保障可用性之前主动测试它。只有最终页面能打开，还不足以说明失败路径没有引入明显延迟，也不能证明用户实际使用了预期传输。&lt;/p&gt;
&lt;p&gt;TLS 恢复可以允许在握手完成前发送零往返应用数据。这不是把 ClientHello 重放一次就自动获得优化；服务端接受的早期请求本身可能被攻击者重放。按 RFC 8470，在没有额外信息时，客户端不得用早期数据发送不安全方法；仅有幂等性质仍不充分。收到 425 后，应在早期数据之外重试，不能把结果不明的购买换成新的业务行动来绕过风险。&lt;/p&gt;
&lt;p&gt;连接标识可以帮助地址变化后的连接迁移，但是否能够继续，取决于协议规则、配置、路由和路径验证。NAT 重绑定与主动迁移相关，却不是同一场景。负载均衡器如果丢失连接归属，流量仍会中断；迁移也不是信任任意新地址或跳过验证的许可。手机切换网络可能保留连接，是有条件的能力，不是所有基础设施都自动兑现的保证。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;将代码保存为 &lt;code&gt;quic_streams.py&lt;/code&gt;，使用 Python 3 执行 &lt;code&gt;python3 quic_streams.py&lt;/code&gt;。程序不打开套接字。它比较单一有序传输与两条流分别排序时的数据可见性。抽象块携带注明位置的字节，不宣称这些块实现真实包格式或恢复算法，也不虚构某个包在第三个时刻丢失的调度过程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Each tuple is a stream name, a one-byte offset, and that byte.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Chunks are abstract delivery units, not QUIC packets or TCP segments.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separate_streams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;buffered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;separate_streams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="c1"&gt;# One ordered transport cannot expose later chunks.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buffered&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# The unit carrying A&amp;#39;s second byte is missing.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;independent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;independent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;xy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;one missing A byte:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;independent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ab&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;xy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;separate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;separate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;after repair: both models expose&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# A single missing delivery unit can contain bytes for both streams.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Even an independently ordered stream cannot skip its own missing prefix.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ab&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;negative controls: shared loss affects both; stream-local gaps still block&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;delivery-order model only; no loss detector, timer, congestion, or wire test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A 的第二个字节缺失时，独立排序模型暴露 A 的第一个字节和完整 B；共享有序传输只暴露 A 的第一个字节。补齐缺失块后，两种结果都完整。输出没有任何实测耗时，也没有通过事先写死的完成时间去“证明”一个协议一定更快。&lt;/p&gt;
&lt;p&gt;负向检查同样重要：丢失一个同时含 A、B 数据的块，会让两条流都缺少必要前缀；只收到 B 的第二个字节，也不能跳过第一个。实验只展示明确写出的排序假设，没有测量重传时间、拥塞、流控耗尽、QPACK、TLS、迁移或浏览器性能。那些结论需要真实实现与另行获准的网络实验，不能由这个内存模型替代。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/zh/posts/http2-multiplexing-fixes-head-of-line/"&gt;HTTP/2 多路复用消除排队&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/zh/posts/tls-1-3-handshake-basics/"&gt;TLS 1.3 握手基础&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9000.html"&gt;RFC 9000：QUIC&lt;/a&gt;，第 2、4、8–9、12–13 节：排序、流控、迁移、包与重传信息，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9002.html"&gt;RFC 9002：QUIC 恢复&lt;/a&gt;，第 6 节：按包编号空间检测丢失及路径级拥塞行为，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9114.html"&gt;RFC 9114：HTTP/3&lt;/a&gt;，HTTP 映射、服务发现及 TCP 回退建议，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9204.html"&gt;RFC 9204：QPACK&lt;/a&gt;，第 2.1.2 节：阻塞的字段区段，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9001.html"&gt;RFC 9001：QUIC 中的 TLS&lt;/a&gt;，第 9.2 节：零往返重放风险，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8470.html"&gt;RFC 8470：HTTP 早期数据&lt;/a&gt;，第 4–5 节：方法安全性及 425 重试行为，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloudflare.com/learning/performance/what-is-http3/"&gt;Cloudflare：HTTP/3 与 QUIC&lt;/a&gt;，协议背景入门，不作为本模型的验证证据，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>