# HTTP的连接管理
30 秒速记
- 短连接:
HTTP/1.0默认一个请求一条TCP连接,握手 + 挥手的开销比传数据还多 - 长连接:
HTTP/1.1默认复用连接,不用写Connection: keep-alive;想关就发Connection: close - 服务器会回收:
Nginx用keepalive_timeout(空闲多久断)和keepalive_requests(一条连接最多处理多少请求) - 长连接的代价:空闲连接也占服务器资源,被恶意大量占用就是攻击
- 队头阻塞:
HTTP/1.1一条连接上请求排队,前面慢后面全等;浏览器同域名最多开6条并发连接来缓解,域名分片是老办法 HTTP/2一条连接多路复用,域名分片反而有害
HTTP 连接管理主要就是长连接和短连接:HTTP/1.0 默认一个请求建一次连接,HTTP/1.1 默认复用同一条 TCP 连接。 建 TCP 连接要三次握手,HTTPS 还要再加 TLS 握手,如果每个请求都重来一遍,大部分时间都花在建连上了,所以 HTTP/1.1 把长连接设成了默认。连接也不能永远挂着,服务器会按空闲时间和处理请求数回收。长连接解决了建连开销,但没解决排队问题:一条连接上同一时间只能跑一个请求,前面那个慢,后面的都得等,这就是队头阻塞,浏览器只好对同一个域名开 6 条连接并发,到 HTTP/2 才用多路复用真正解决。
连接管理的三种形态,用时间线对比最直观:
- 短连接:建连 → 请求 → 响应 → 断开,再建连 → 请求 → 响应 → 断开。每个请求都要付一次
TCP三次握手(约1个RTT)和四次挥手的代价,HTTPS还要加TLS握手。HTTP/1.0默认就是这样,要复用得显式发Connection: keep-alive。 - 长连接(持久连接):建连一次,后面的请求排着队用同一条连接,最后空闲超时或主动
Connection: close再断开。HTTP/1.1默认就是长连接。 - 多路复用:
HTTP/2把请求拆成带编号的帧,在一条连接上交错发送,谁先好谁先回,应用层不再排队。
长连接的边界有三点:
- 服务器要控制连接数量和寿命。
Nginx里keepalive_timeout控制空闲多久断开(默认75s),keepalive_requests控制一条连接最多处理多少个请求(1.19.10之后默认1000,之前是100)。 - 队头阻塞没解决。
HTTP/1.1管线化(pipelining)理论上允许连发多个请求,但响应必须按顺序回,主流浏览器都没默认启用。所以浏览器对同一个域名最多开6条连接,资源多了就排队。 HTTP/2解决了应用层的队头阻塞,但所有请求挤在一条TCP连接上,丢一个包整条连接都要等重传。HTTP/3换成基于UDP的QUIC,每个流独立重传,连TCP层的阻塞也绕开了。
在 Node 里可以直观看到连接复用:
const http = require('http')
const agent = new http.Agent({ keepAlive: true }) // 开启连接复用
http.get({ host: 'example.com', agent }, res => {
res.resume()
res.on('end', () => {
// 第二个请求会复用刚才那条 socket,不再握手
http.get({ host: 'example.com', agent }, r => console.log(r.statusCode))
})
})
Node 19 起全局默认的 http.globalAgent 已经开启了 keepAlive,老版本要像上面这样手动传 agent。
面试官追问
追问 1前端给所有请求加上 Connection: keep-alive,能保证复用连接吗?
加不了也没必要。浏览器里 Connection 是禁止修改的头,HTTP/1.1 本来就默认长连接,HTTP/2 里这个头甚至是不允许出现的。连接能不能复用看浏览器和服务器、代理的配置,在 Network 面板打开 Connection ID 列就能看到是不是同一条。
追问 2网关上大量空闲连接,新请求连不上,有人要把 keepalive_timeout 调得更长,对吗?
方向反了。空闲连接多说明保活时间太长、占着资源不干活,应该适当调短,同时检查 worker_connections 等连接上限够不够。Nginx 默认 keepalive_timeout 是 75s,前端页面一般十几秒就够了。
追问 3改成多个静态资源域名后首屏变快了,服务器连接数也涨了,要保留吗?
先看协议版本。还是 HTTP/1.1 的话域名分片确实能突破 6 条的限制,可以留;已经上了 HTTP/2 就该合并回来,多域名会多做 DNS 解析和 TLS 握手,还用不上同一条连接的多路复用。
追问 4一个慢接口后面的图片请求迟迟不返回,怎么确认是不是队头阻塞?
打开 Network 看 Protocol 列和 Timing。HTTP/1.1 下图片如果大段时间花在 Stalled / Queueing 上,说明在等可用连接;HTTP/2 下应用层不会这样排队,那就要看是不是服务端本身慢或者丢包导致的 TCP 层阻塞。
追问 5浏览器关掉页面后,服务端还能看到连接存在,是谁泄漏了?
不一定是泄漏。浏览器可能还在复用这条连接给同域的其他页面用,中间有代理的话,服务端看到的是代理到后端的连接,和浏览器那条不是同一条,代理通常有自己的连接池。超过保活时间还不断,才值得查。
# 短连接
30 秒速记
- 短连接 = 一个请求一条
TCP连接,响应完立刻关,HTTP/0.9、HTTP/1.0默认就这么干 - 一次完整流程:三次握手 → 发请求 → 收响应 → 四次挥手,下一个请求从头再来
- 按简化模型算:握手
1 RTT+ 挥手2 RTT,请求应答本身最多2 RTT,3/5 = 60%的时间花在开关连接上 - 请求越小越多,浪费比例越高;一个页面几十个小图标是重灾区
- 「无连接」说的是不保持连接,不是不建连接,底下照样走
TCP
短连接就是每发一个 HTTP 请求都单独建一条 TCP 连接,拿到响应马上关掉。 打个比方,打卡机罩着个很重的盖子,每个人都要开盖、打卡、关盖,打卡 1 秒,开关盖子 5 秒,上班高峰自然排长队。换算到网络上,握手要 1 RTT、挥手要 2 RTT,真正的请求应答反而是小头。所以早期页面资源一多就慢,后来 HTTP/1.1 才把长连接设成默认。面试里被问「HTTP 是无连接的」,记得补一句:它只是不长期占着连接,底层还是要先建 TCP。
HTTP 协议最初(0.9/1.0)是个非常简单的协议,通信过程也采用了简单的“请求 - 应答”方式。
它底层的数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接。
因为客户端与服务器的整个连接过程很短暂,不会与服务器保持长时间的连接状态,所以就被称为“短连接”(short-lived connections)。早期的 HTTP 协议也被称为是“无连接”的协议。
短连接的缺点相当严重,因为在 TCP协议里,建立连接和关闭连接都是非常“昂贵”的操作。TCP 建立连接要有“三次握手”,发送 3 个数据包,需要 1 个 RTT;关闭连接是“四次挥手”,4 个数据包需要 2 个 RTT。
而 HTTP 的一次简单“请求 - 响应”通常只需要 4 个包,如果不算服务器内部的处理时间,最多是 2 个 RTT。这么算下来,浪费的时间就是“3÷5=60%”,有三分之二的时间被浪费掉了,传输效率低得惊人

单纯地从理论上讲,TCP 协议你可能还不太好理解,我就拿打卡考勤机来做个形象的比喻吧。
假设你的公司买了一台打卡机,放在前台,因为这台机器比较贵,所以专门做了一个保护罩盖着它,公司要求每次上下班打卡时都要先打开盖子,打卡后再盖上盖子。
可是偏偏这个盖子非常牢固,打开关闭要费很大力气,打卡可能只要 1 秒钟,而开关盖子却需要四五秒钟,大部分时间都浪费在了毫无意义的开关盖子操作上了。
可想而知,平常还好说,一到上下班的点在打卡机前就会排起长队,每个人都要重复“开盖 - 打卡 - 关盖”的三个步骤,你说着急不着急。
在这个比喻里,打卡机就相当于服务器,盖子的开关就是 TCP 的连接与关闭,而每个打卡的人就是 HTTP 请求,很显然,短连接的缺点严重制约了服务器的服务能力,导致它无法处理更多的请求。
面试官追问
追问 1都说早期 HTTP 是「无连接」的,那它发请求前还用握手吗?
要。「无连接」只是说响应完不保留连接、下次重新建,HTTP 跑在 TCP 上,每次都得先三次握手。很多人把这俩概念搅在一起,就漏算了建连成本。
追问 2一个页面要拉 40 个小图标,服务端每个只处理 2ms,整体还是慢,短连接下慢在哪?
慢在 40 次握手和挥手。假设 RTT 是 50ms,每个图标光开关连接就要 150ms,服务端那 2ms 根本排不上号。打开 DevTools 的 Timing,能看到每个请求都有一段 Initial connection。
追问 3一个每天只调一次的内部接口用的短连接,要不要专门改?
一般不改。短连接的代价跟请求频率成正比,一天一次的调用多花一百多毫秒没人感知得到。精力留给那些高频、小包、跨机房的调用。
