<?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>Http3 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/http3/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/http3/index.xml" rel="self" type="application/rss+xml"/><item><title>HTTP3 走 UDP，HTTP2 仍走 TCP 兜底</title><link>https://www.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</link><pubDate>Sat, 12 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/nginx-http3-http2-fallback/</guid><description>HTTP/3 用基于 UDP 的 QUIC 换掉 TCP 头几行阻塞，HTTP/2 仍是老客户端的可靠兜底。</description><content:encoded>&lt;p&gt;HTTP/3 把 QUIC 跑在 UDP:443 上，值得在意的理由是丢一个包的代价。TCP 连接上所有流共用一个字节流，丢一个段就会挡住它后面所有数据的交付，直到重传到达。QUIC 让每条流各自拥有丢失恢复，同一个丢包只挡住数据真的在那个包里的流。&lt;/p&gt;
&lt;p&gt;别扭的地方在于这不是一个开关。客户端必须先知道服务器会说 HTTP/3 才可能去试，而它得知这件事所走的那条路径，恰恰是你想优化的那条。&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; 并排放着，用同一个端口号同时服务 QUIC 和 HTTP/2。&lt;code&gt;reuseport&lt;/code&gt; 比看起来更重要：没有它，第二次绑定 UDP:443 会拿到 EADDRINUSE，而这正是两个 server 块都声称要占用 QUIC 监听时看到的报错。&lt;/p&gt;
&lt;p&gt;QUIC 不是「HTTP/2 搬到 UDP 上」。它把传输握手和 TLS 握手合成一趟——一个往返，复用会话时是零个——并且携带一个与地址和端口无关的连接 ID。这个 ID 让连接迁移成为可能，同时也让那些假定一条流永远待在一个五元组上的负载均衡器当场失效。还要记住 QUIC 要求整条路径都能过 UDP：企业网络、机场 Wi-Fi 和部分移动运营商至今仍在整段丢弃 443 端口的 UDP，这正是兜底路径必须一直可用的原因。&lt;/p&gt;
&lt;h2 id="alt-svc-是客户端得知的方式"&gt;Alt-Svc 是客户端得知的方式&lt;/h2&gt;
&lt;p&gt;对某个源的第一次连接永远是走 TCP 的 HTTP/2，因为客户端此时一无所知。响应里带着一条通告：&lt;code&gt;Alt-Svc: h3=&amp;quot;:443&amp;quot;; ma=86400&lt;/code&gt;。解析开来就是协议 &lt;code&gt;h3&lt;/code&gt;、authority &lt;code&gt;:443&lt;/code&gt;、新鲜期 86400 秒，也就是 24 小时。下一次连接该源时客户端会先试 QUIC，所以升级发生在后续某次访问，永远不会发生在第一次。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ma&lt;/code&gt; 才是咬人的地方。只要通告还在新鲜期内，客户端每建立一条新连接都会先试 HTTP/3——哪怕 HTTP/3 已经反复失败。把 UDP:443 被中间盒静默丢弃的情形建个模型：客户端在 QUIC 探测超时退避里烧掉 15 秒（1、2、4 再到 8 秒）才回退到 HTTP/2。按每天 20 次访问算，就是每天白白丢掉 300 秒，一直持续到一天后缓存过期。反过来，不等 QUIC 放弃就并行发起 TCP 连接，同一条被阻断的路径 0.1 秒就能出结果。回退的代价是设计选择，不是协议开销。&lt;/p&gt;
&lt;h2 id="0-rtt-与连接迁移是两处锋利的边缘"&gt;0-RTT 与连接迁移是两处锋利的边缘&lt;/h2&gt;
&lt;p&gt;0-RTT 允许回访客户端在第一个飞行包里就发出应用数据，用的是上一会话留下的票据。这些数据可以被重放——抓到那个飞行包的人可以再发一次——所以只有可以安全重复的请求才该放进去。把「创建订单」放在 0-RTT 里，等于预订了一笔重复订单。&lt;/p&gt;
&lt;p&gt;连接迁移是另一处。QUIC 用连接 ID 而不是地址来标识连接，所以从 Wi-Fi 漫游到蜂窝网的客户端能保住同一条连接。把一台按五元组做哈希的负载均衡器放在前面，这条继续存在的连接就会落到另一个后端：八个后端的情况下，把同一客户端漫游前后的键算出来，先落到 7 号，再落到 4 号。协议的移动性变成了负载均衡器的问题。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;第一种失败，是客户端缓存着 Alt-Svc，而网络把 UDP 丢了。它的表象是整整一天里每次导航首字节都很慢，而服务器日志里没有任何异常——QUIC 的 Initial 根本没到，也就没有东西可记。把服务端的 HTTP/3 连接数和客户端感知到的延迟放在一起比；h2 路径健康却出现巨大落差，就是它的特征。缓解办法是缩短 &lt;code&gt;ma&lt;/code&gt;、并行竞速 TCP 连接，以及不要在你验证不了的路径上通告 h3。&lt;/p&gt;
&lt;p&gt;第二种失败，是连接迁移打坏了会话亲和性。移动客户端在会话中途换地址，HTTP/3 把连接保住了，而此刻接管它的后端没有任何原先那台持有的缓存或会话状态。它的表象是间歇性的认证失败、缓存不命中、上传做了一半，而且只出现在移动端客户端上——这也是它难以复现的原因。按连接 ID 路由，或者把亲和性钉在一个能扛住地址变化的东西上。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;确认两条路径都真的活着：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -sI https://example.com/ | grep -i alt-svc
curl -s --http3-only -o /dev/null -w &amp;#39;%{http_version} %{time_appconnect}\n&amp;#39; https://example.com/
nginx -T 2&amp;gt;/dev/null | grep -nE &amp;#39;listen .*quic|http3|Alt-Svc&amp;#39;
ss -lun | grep -E &amp;#39;:443\b&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第一条应当打出一条带有限 &lt;code&gt;ma&lt;/code&gt; 的 &lt;code&gt;h3&lt;/code&gt; 通告。第二条在 UDP 通的时候打出 &lt;code&gt;3&lt;/code&gt; 和很小的连接耗时，路径被挡时则直接失败——第一条成功而这里失败，就是典型的中间盒情形。第三条看配置是否真把 QUIC 和响应头接在了一起，第四条证明 443 上确实有一个 UDP 套接字被绑定。&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;/ul&gt;</content:encoded></item><item><title>HTTP/3 分离流的交付顺序，不隔离全部恢复机制</title><link>https://www.xbcatm.com/zh/posts/http3-and-quic-recover-per-stream/</link><pubDate>Sun, 21 Mar 2027 00:00:00 +0000</pubDate><guid>https://www.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;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://www.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://www.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>