bigywater.tech
网络协议 · 75

HTTP/1.1 持久连接与流水线:连接复用与它的限制

一个页面要取很多资源,每个资源都新建一条 TCP 连接太贵,所以有了持久连接和(曾经的)流水线。这篇在 100 ms 往返的链路上量:复用连接省了多少个往返、流水线又省了多少、它为什么没有被普遍使用——顺便抓到了一次真实的 Nagle 算法拖慢。

2024 → 1114 → 233 ms
10 个资源:每次新建 / keep-alive 顺序 / 流水线 的总耗时(往返 100 ms)
702 vs 202 ms
流水线里快请求被慢请求拖住的时刻 与 并行连接里的到达时刻
100 ms
服务端不开 TCP_NODELAY 时,流水线后几个响应额外多等的时间(一个 RTT)

复用连接

实测 · 本机执行输出
  每次新建连接:10 个资源总共 2024 ms(≈ 10 × (握手 1 个 RTT + 请求响应 1 个 RTT) = 10 × 200 ms)
  同一条连接上依次取:总共 1114 ms(≈ 握手 1 RTT + 10 × 1 RTT = 11 × 100 ms)
  省下 910 ms,约 9.1 个 RTT;每个资源平均 202 ms 对 111 ms

每次新建要 2 个 RTT,keep-alive 只要 1 个。10 个资源省下约 9 个 RTT。

流水线

实测 · 本机执行输出
  流水线:10 个资源总共 233 ms(握手 1 RTT + 一次请求响应 1 RTT ≈ 200 ms);第 1 个响应在握手后 100 ms,第 10 个在 133 ms
  对比:非流水线的 keep-alive 1114 ms;每次新建 2024 ms

不等响应连续发请求,10 个资源总共约 2 个 RTT。

队头阻塞

实测 · 本机执行输出
  同一条连接流水线(第 1 个慢),服务器默认(Nagle 开):5 个响应到达时刻(从握手完成起,ms):[702, 802, 802, 802, 802]
  同一条连接流水线(第 1 个慢),服务器 TCP_NODELAY:[704, 704, 704, 704, 704]
  5 条并行连接(含握手):到达时刻 [806, 202, 202, 202, 202]
  快请求在流水线里被拖到 704 ms(NODELAY)之后,在并行连接里 202~202 ms 就拿到了;Nagle 开时后 4 个比第 1 个晚了 100 ms,NODELAY 时晚了 0 ms

响应必须按请求顺序返回,第一个慢,后面的都得等;并行连接则互不影响。服务端不开 TCP_NODELAY 还会再慢一个 RTT。

真实服务器

实测 · 本机执行输出
  3 个请求一次发出,3 秒内收到 2799 字节,其中 "HTTP/1.1 200 OK" 状态行 3 个;状态行们:[b'HTTP/1.1 200 OK', b'HTTP/1.1 200 OK', b'HTTP/1.1 200 OK']

example.com 能处理一次发出的 3 个流水线请求。

空闲超时

实测 · 本机执行输出
  响应头里和连接有关的:Connection = keep-alive,Keep-Alive = None
  130 秒内服务器一直没有关闭连接

example.com 的持久连接空闲 130 秒仍未被关闭。

分步演示

持久连接与流水线
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。第一至三节用本机的 Python 服务器(线程,每个请求顺序处理)和 netlab(每方向 50 ms);第三节分别让服务端默认与 TCP_NODELAY;第四、五节是对 example.com:80 的明文 HTTP/1.1,第五节只等了 130 秒
    • 没有验证:浏览器每个主机的并发连接数(通常 6);HTTP/1.1 的 Keep-Alive: timeout/max 参数在别的服务器上的行为;连接被中间设备悄悄丢掉后客户端的表现;流水线遇到不支持它的代理时的失败方式;服务器对每条连接最大请求数的限制;第二节第 10 个响应比第 1 个晚 33 ms 的原因(没有查)
    京ICP备2026067502号-1京公网安备11010502063487号