HTTP Keep-Alive,也叫 HTTP 持久连接,核心作用是:让多个 HTTP 请求复用同一条 TCP 连接,避免每次请求都重新建立连接。
一、为什么需要 Keep-Alive?
一个网页通常需要请求 HTML、CSS、JavaScript、图片等许多资源。
1. 没有 Keep-Alive 时
如果不使用 Keep-Alive,每个 HTTP 请求都需要单独建立和关闭 TCP 连接:
text
建立 TCP 连接 1
发送 GET /index.html
接收响应
关闭 TCP 连接 1
建立 TCP 连接 2
发送 GET /logo.png
接收响应
关闭 TCP 连接 2
建立 TCP 连接 3
发送 GET /background.png
接收响应
关闭 TCP 连接 3
请求三个资源,就建立三条 TCP 连接。每次都要经历 TCP 握手和连接关闭;使用 HTTPS 时,还会增加 TLS 握手开销。
2. 使用 Keep-Alive 时
使用 Keep-Alive 后,同一条 TCP 连接可以依次承载多个 HTTP 请求和响应:
text
建立 TCP 连接
发送 GET /index.html
← 接收 index.html
发送 GET /logo.png
← 接收 logo.png
发送 GET /background.png
← 接收 background.png
一段时间没有新请求
关闭 TCP 连接
它们之间的关系是:
text
一条 TCP 连接
├── HTTP 请求 1 + 响应 1
├── HTTP 请求 2 + 响应 2
└── HTTP 请求 3 + 响应 3
Keep-Alive 不是让某一个 HTTP 响应一直不结束,而是让底层 TCP 连接在一次响应结束后继续存活,供后续 HTTP 请求复用。
二、HTTP/1.0 和 HTTP/1.1 的区别
HTTP/1.0 默认在一次请求和响应结束后关闭 TCP 连接。如果希望复用连接,通常需要明确声明:
http
GET /index.html HTTP/1.0
Host: example.com
Connection: keep-alive
HTTP/1.1 默认使用持久连接。即使请求中没有写 Connection: keep-alive,通常也会保留连接:
http
GET /index.html HTTP/1.1
Host: example.com
如果客户端或服务器不希望继续复用,需要明确声明:
http
GET /index.html HTTP/1.1
Host: example.com
Connection: close
http
HTTP/1.1 200 OK
Connection: close
Connection 是逐跳请求头,只对当前客户端与下一跳服务器或代理之间的连接有效,不是端到端请求头。
三、一次 Keep-Alive 请求是怎样运行的?
客户端首先建立 TCP 连接,然后发送第一个请求:
http
GET /index.html HTTP/1.1
Host: example.com
服务器返回:
http
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
hello
客户端根据 Content-Length: 5 读取完 hello 后,知道这次 HTTP 响应已经结束。
此时 TCP 连接没有关闭,客户端可以在同一条连接上继续发送:
http
GET /user HTTP/1.1
Host: example.com
服务器可以继续返回:
http
HTTP/1.1 200 OK
Content-Length: 6
world
整个过程如下:

如果客户端准备发送下一次请求时发现连接已经关闭,就会重新建立 TCP 连接。
四、消息边界很重要
多个请求和响应共用一条 TCP 连接时,接收方必须知道:当前消息在哪里结束,下一条消息从哪里开始?
这就是 HTTP 消息边界问题
1. 使用 Content-Length
Content-Length 用来声明响应体的总字节数
http
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
hello world
即使指定了 Content-Length,Body 也不需要一次性发送完。服务端可以在同一个 HTTP 响应中分多次写入 Body
例如服务端的代码可以是:
javascript
res.write("hello");
// 等待 1 秒
res.write(" world");
res.end();
无论服务端分几次写入,客户端累计读取满11字节后,就知道响应结束了,接下来读取的内容属于下一条 HTTP 消息
2. 使用分块传输(Transfer-Encoding: chunked)
如果服务端不能提前确定响应体总大小时,可以分块写入,并在内容结束时使用分割符
例如服务端的代码可能是:
javascript
res.write("hello");
// 等待 1 秒
res.write(" world");
res.end();
实际的响应为
http
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
5
hello
6
world <- 注意这里有个空格,所以是6个字节
0
<- 注意这里有个空行
块长度使用十六进制,0\r\n\r\n 表示响应体结束
分块响应结束后,TCP 连接不会因此自动关闭,仍然可以继续承载下一次 HTTP 请求和响应
3. 关闭 TCP 连接
某些响应既没有 Content-Length,也没有分块编码,客户端只能通过 TCP 连接关闭判断响应体结束:
text
读取响应体 → 收到 TCP FIN → 响应结束
这种方式关闭了底层连接,所以无法继续复用。
4. 协议规定没有响应体
部分响应不包含消息体,因此读完响应头就结束,例如:
HEAD请求的响应204 No Content304 Not Modified- 部分
1xx响应
五、Keep-Alive 不会永久保持连接
HTTP 持久连接通常会受到以下限制:
- 空闲超时:一段时间没有新请求,服务器或代理就关闭连接
- 最大请求数:一条连接处理一定数量的请求后关闭
- 服务端负载:服务器资源紧张时可能主动回收空闲连接
- 中间网络设备:反向代理、负载均衡器或防火墙可能提前关闭连接
服务器可能返回类似的提示:
http
Connection: keep-alive
Keep-Alive: timeout=5, max=100
大致表示连接空闲 5 秒后可能被关闭,并且最多处理 100 个请求。不过这些值通常只是提示,实际行为取决于服务器和中间代理的配置
服务器如果决定处理完当前响应后关闭连接,可以返回:
http
Connection: close
客户端读取完本次响应后,不应再通过该连接发送新请求
六、HTTP/1.1 Keep-Alive 的局限
HTTP/1.1 可以复用 TCP 连接,但通常仍然按照下面的顺序通信:
text
请求 1 → 响应 1 → 请求 2 → 响应 2
HTTP/1.1 还设计过管线化,可以连续发送多个请求:
text
请求 1
请求 2
请求 3
响应 1
响应 2
响应 3
但服务器必须按照请求顺序返回响应。如果响应 1 很慢,响应 2 和响应 3 即使已经准备好,也不能越过响应 1,这叫作应用层的队头阻塞。
因此,浏览器通常不会使用 HTTP/1.1 管线化,而是会为同一个域名建立多条 TCP 连接。
HTTP/2 支持在一条连接上并发传输多个请求和响应:
text
一条 TCP 连接
├── Stream 1:请求/响应 A
├── Stream 3:请求/响应 B
└── Stream 5:请求/响应 C
这叫多路复用。不同请求和响应的数据帧可以交错传输,不再要求完成一个响应后才能传输下一个响应。
七、总结
HTTP Keep-Alive 允许多个 HTTP 请求和响应复用同一条 TCP 连接。一次响应结束后,连接不会立即关闭,而是继续等待后续请求,直到客户端、服务器或中间代理将其关闭。
连接复用的前提是每条 HTTP 消息都有明确的边界。边界通常由 Content-Length、Transfer-Encoding: chunked、协议规定的无响应体语义或连接关闭来确定。
✨ 微信公众号【凉凉的知识库】同步更新,欢迎关注获取最新最有用的知识 ✨