HTTP 的 Keep-Alive 长连接是什么?分块传输编码(chunked)又是怎么回事?
简化版
Keep-Alive(长连接)和分块传输编码(chunked)是 HTTP 的两个重要机制,都和「连接复用、响应传输」有关。① Keep-Alive(持久连接/长连接)——HTTP/1.0 默认每个请求都新建一个 TCP 连接、用完就关(短连接),开销大(每次三次握手);HTTP/1.1 默认开启 Keep-Alive,让「一个 TCP 连接可以复用、连续处理多个请求」(一次连接建立,多次请求响应,不用每次握手),大幅减少连接开销,通过 Connection: keep-alive 头控制。② 分块传输编码(Transfer-Encoding: chunked)——正常响应要在头里用 Content-Length 声明「响应体多大」,但有时服务端不知道响应体总大小(如动态生成、流式输出、边算边发),这时用 chunked:把响应体拆成一个个「块(chunk)」,每块前面标明这块多大,一块块发,最后发一个「0 大小的块」表示结束——不用预先知道总大小。为什么它俩相关:Keep-Alive 复用连接的前提是「知道一个响应在哪结束」(才能接着处理下一个请求),Content-Length 或 chunked 都能标明响应边界。核心:Keep-Alive 复用 TCP 连接减少握手开销(HTTP/1.1 默认);chunked 在不知道响应总大小时分块传输(每块标大小、0 块结束)。
详细版
短连接 vs 长连接:
| 维度 | 短连接(HTTP/1.0 默认) | 长连接(Keep-Alive,HTTP/1.1 默认) |
|---|---|---|
| 连接 | 每个请求新建、用完关 | 一个连接复用多个请求 |
| 握手 | 每次都三次握手(开销大) | 建立一次、复用(省握手) |
| 头 | Connection: close | Connection: keep-alive |
| 性能 | 差(频繁建连) | 好(复用连接) |
短连接(HTTP/1.0):
请求1 → 建连 → 响应1 → 关连
请求2 → 建连 → 响应2 → 关连 (每次都建连、关连)
长连接(Keep-Alive):
建连 → 请求1 → 响应1 → 请求2 → 响应2 → ... → 关连
(一个连接处理多个请求,省了多次握手)
分块传输(Transfer-Encoding: chunked):
HTTP/1.1 200 OK
Transfer-Encoding: chunked
7\r\n ← 这块 7 字节(十六进制)
Mozilla\r\n ← 块内容
9\r\n ← 下一块 9 字节
Developer\r\n
0\r\n ← 0 大小的块 = 结束
\r\n
// Tomcat 配置 Keep-Alive(server.xml Connector)
<Connector port="8080"
maxKeepAliveRequests="100" // 一个连接最多处理多少个请求(-1 无限)
keepAliveTimeout="60000" // 长连接空闲多久关闭(毫秒)
connectionTimeout="20000" />
⚠️ 理解 Keep-Alive 和 chunked 的联系:「连接复用」需要「知道每个响应在哪结束」,而
Content-Length和chunked是标明响应边界的两种方式。为什么长连接需要响应边界:一个 TCP 连接连续处理多个请求响应,服务端/客户端必须知道「上一个响应到哪结束」,才能开始读下一个响应——如果不知道边界,就分不清「这个响应的数据」和「下一个响应的数据」。方式一:Content-Length——响应头声明响应体的确切字节数,读够这么多字节就是一个响应结束(适合已知大小的响应)。方式二:Transfer-Encoding: chunked——服务端不知道总大小时(动态生成、流式、边算边发,如大文件下载、SSE、大 JSON 流式输出),用分块编码:每块前标明块大小、一块块发、最后一个「0 大小的块」表示结束——接收方读到 0 块就知道响应结束了。注意:Content-Length和chunked二选一(有 chunked 就不用 Content-Length,因为总大小未知)。HTTP/2 有自己的帧机制(多路复用),不用这套(但概念相通)。
完整版教学
一、短连接的问题
先理解为什么需要 Keep-Alive(短连接的问题):
HTTP/1.0 默认短连接:
每个 HTTP 请求都新建一个 TCP 连接,请求-响应完成后关闭连接
请求1 → 建 TCP 连接(三次握手)→ 响应1 → 关闭(四次挥手)
请求2 → 又建 TCP 连接 → 响应2 → 又关闭
问题(连接开销大):
① 每次请求都要三次握手(建连)+ 四次挥手(关连)
→ 握手/挥手的往返时延(RTT)开销
② 一个网页有很多资源(HTML、CSS、JS、图片)
→ 每个资源一个请求 → 每个都建连关连 → 大量握手开销
③ TCP 慢启动——新连接的传输速度要慢慢加速
→ 短连接每次都慢启动,用不到高速
想要的:
复用连接——一个 TCP 连接处理多个请求
→ 省去多次握手、避免反复慢启动
所以短连接的问题 = 每次请求建连关连,握手开销大、慢启动
→ 需要长连接(复用连接)
短连接的问题(为什么需要 Keep-Alive)——HTTP/1.0 默认短连接(每个请求新建 TCP 连接、用完关闭)。问题:① 每次请求三次握手+四次挥手(RTT 开销)、② 一个网页很多资源每个建连关连(大量握手开销)、③ TCP 慢启动(短连接每次慢启动用不到高速)。想要的:复用连接(一个 TCP 连接处理多个请求,省握手、避免反复慢启动)。理解「短连接问题:每次请求建连关连(三次握手四次挥手 RTT 开销)、网页很多资源每个建连、TCP 慢启动每次都慢;需要长连接复用连接省握手」,就理解了为什么需要 Keep-Alive。
二、Keep-Alive:长连接
Keep-Alive(长连接)——复用 TCP 连接:
Keep-Alive(持久连接/长连接):
一个 TCP 连接建立后,可以连续处理多个请求(不关闭)
建连(一次)→ 请求1 → 响应1 → 请求2 → 响应2 → ... → 关闭
→ 一次握手,多次请求响应(复用连接)
HTTP 版本的差异:
HTTP/1.0:默认短连接,要 Keep-Alive 得加 Connection: keep-alive 头
HTTP/1.1:默认开启 Keep-Alive(不用显式加,除非要关 Connection: close)
好处:
① 省握手——一个连接多个请求,不用每次三次握手
② 避免反复慢启动——连接热了(传输速度上来了)继续用
→ 性能明显提升(尤其一个页面很多资源)
控制(响应/请求头):
Connection: keep-alive:保持长连接
Connection: close:关闭连接(不复用)
Keep-Alive: timeout=5, max=100:超时和最大请求数(可选)
长连接的管理(服务端):
连接不能永远开着(占资源)→ 有超时和最大请求数
keepAliveTimeout:空闲多久关闭
maxKeepAliveRequests:一个连接最多处理多少请求
HTTP/1.1 的局限(队头阻塞):
Keep-Alive 复用连接,但同一连接的请求是"串行"的
(一个响应没回完,下一个请求要等)→ 队头阻塞
HTTP/2 用多路复用解决(一个连接并发多个请求)
所以 Keep-Alive = 复用 TCP 连接(HTTP/1.1 默认),省握手、避免慢启动
Keep-Alive(长连接)——一个 TCP 连接建立后连续处理多个请求(不关闭、复用)。HTTP 版本差异:HTTP/1.0 默认短连接(要加 Connection: keep-alive)、HTTP/1.1 默认开启 Keep-Alive。好处:省握手、避免反复慢启动(性能提升)。控制:Connection: keep-alive/close。管理:keepAliveTimeout(空闲多久关)、maxKeepAliveRequests(最多处理几个请求)。局限(队头阻塞):同一连接的请求串行(一个没回完下一个要等)、HTTP/2 用多路复用解决。理解「Keep-Alive 长连接复用 TCP 连接(HTTP/1.1 默认、1.0 要加头);好处省握手+避免慢启动;控制 Connection: keep-alive/close;管理 keepAliveTimeout/maxKeepAliveRequests;局限队头阻塞(请求串行)HTTP/2 解决」,就掌握了 Keep-Alive。
三、响应边界的问题
理解「长连接为什么需要响应边界」:
长连接复用连接的前提:知道每个响应在哪结束
一个连接连续处理多个请求响应:
...响应1的数据...响应2的数据...
接收方必须知道"响应1到哪结束"
→ 才能开始读响应2(分清哪些数据属于哪个响应)
如果不知道响应边界:
数据连成一片,分不清"响应1"和"响应2"的界限
→ 无法正确复用连接
标明响应边界的两种方式:
① Content-Length:响应头声明响应体的确切字节数
Content-Length: 1024 → 读够 1024 字节就是响应结束
→ 适合"已知大小"的响应
② Transfer-Encoding: chunked:分块传输
→ 每块标大小、0 块表示结束
→ 适合"不知道总大小"的响应(下节)
短连接不需要响应边界:
短连接一个连接一个响应,连接关闭就是响应结束
→ 不用标边界(关闭 = 结束)
但长连接复用,必须标边界
所以长连接需要响应边界(Content-Length 或 chunked)才能复用连接
「长连接为什么需要响应边界」——长连接复用连接,一个连接连续处理多个响应,接收方必须知道每个响应在哪结束才能读下一个(不知道边界数据连成一片分不清)。标明响应边界两种方式:① Content-Length(声明响应体确切字节数、读够就结束,适合已知大小)、② Transfer-Encoding: chunked(分块传输、每块标大小 0 块结束,适合不知道总大小)。短连接不需要边界(连接关闭就是响应结束)。理解「长连接需要响应边界:一个连接多个响应、接收方必须知道每个响应在哪结束才能读下一个;两种方式 Content-Length(已知大小)或 chunked(不知道大小);短连接不需要(关闭=结束)」,就理解了响应边界的问题。
四、分块传输编码
分块传输编码(chunked)——不知道总大小时分块传:
问题:服务端不知道响应体总大小怎么办?
场景:
① 动态生成——边生成边发(不知道最终多大)
② 流式输出——大文件流式下载、SSE 事件流
③ 大数据——大 JSON 边序列化边发
→ 无法预先算出 Content-Length(总大小未知)
分块传输编码(Transfer-Encoding: chunked):
把响应体拆成一个个"块(chunk)",每块独立发送
格式:
每块 = 块大小(十六进制)+ \r\n + 块内容 + \r\n
最后 = 0(0 大小的块)+ \r\n + \r\n(表示结束)
例:
Transfer-Encoding: chunked
7\r\n ← 块大小 7(十六进制)
Mozilla\r\n ← 7 字节内容
9\r\n ← 块大小 9
Developer\r\n ← 9 字节内容
0\r\n ← 0 大小的块 = 结束
\r\n
优点:
① 不用预先知道总大小——边生成边发(流式)
② 每块标自己的大小——接收方读每块、读到 0 块就结束
→ 支持流式响应(大文件、实时数据、边算边发)
Content-Length vs chunked(二选一):
Content-Length:已知大小,声明总字节数
chunked:未知大小,分块发、0 块结束
→ 有 chunked 就不用 Content-Length(互斥)
接收方处理:
一块块读(读块大小、读块内容),读到 0 块 → 响应结束
所以 chunked = 不知道总大小时分块传(每块标大小、0 块结束、支持流式)
分块传输编码(chunked)——服务端不知道响应体总大小时(动态生成/流式输出/大数据边算边发、无法预先算 Content-Length)。Transfer-Encoding: chunked:把响应体拆成一个个「块」,每块 = 块大小(十六进制)+内容,最后一个 0 大小的块表示结束。优点:不用预先知道总大小(边生成边发、流式)、每块标自己大小(接收方读到 0 块结束)。Content-Length vs chunked 二选一(有 chunked 不用 Content-Length、互斥)。理解「chunked 分块传输:服务端不知道总大小时(动态生成/流式/大数据)、把响应体拆成块(每块标大小+内容、0 大小块结束);优点不用预先知道总大小(流式)、每块标大小接收方读到 0 块结束;Content-Length(已知大小)vs chunked(未知)二选一」,就掌握了分块传输编码。
五、应用场景
理解 Keep-Alive 和 chunked 的应用场景:
Keep-Alive 的应用:
① 网页加载(HTML + 很多 CSS/JS/图片)
→ 复用连接加载所有资源(省大量握手)
② API 调用(一个客户端多次请求同一服务)
→ 复用连接
③ 几乎所有 HTTP 通信(HTTP/1.1 默认开)
chunked 的应用:
① 流式下载大文件——边读边发(不用先算文件大小)
② SSE(Server-Sent Events)——服务端持续推送事件流
→ chunked 让连接保持、持续发数据
③ 大 JSON/数据流式输出——边序列化边发(省内存)
④ 动态生成的内容——边生成边发(不知道最终大小)
⑤ 长轮询、流式 API
两者配合:
Keep-Alive 复用连接 + chunked 标响应边界(未知大小时)
→ 一个长连接,多个响应,有的用 Content-Length、有的用 chunked
现代(HTTP/2、HTTP/3):
HTTP/2 用二进制帧 + 多路复用(一个连接并发多请求,解决队头阻塞)
→ 不用 chunked 这套(有自己的帧机制),但概念相通
→ HTTP/2 的连接复用更强(并发而非串行)
所以 Keep-Alive 复用连接(几乎所有 HTTP)、chunked 流式传输(未知大小)
Keep-Alive 和 chunked 的应用:Keep-Alive(网页加载复用连接省握手、API 调用复用、几乎所有 HTTP 通信 HTTP/1.1 默认);chunked(流式下载大文件边读边发、SSE 持续推送事件流、大 JSON 流式输出省内存、动态生成边生成边发、长轮询)。两者配合(Keep-Alive 复用连接 + chunked 标未知大小的响应边界)。现代(HTTP/2) 用二进制帧+多路复用(一个连接并发多请求解决队头阻塞、不用 chunked 这套但概念相通、连接复用更强)。理解「Keep-Alive 应用:网页加载/API 调用/几乎所有 HTTP;chunked 应用:流式下载/SSE 事件流/大 JSON 流式/动态生成/长轮询;两者配合;HTTP/2 用帧+多路复用更强」,就掌握了应用场景。
六、实践与总结
总结 Keep-Alive 和 chunked 的实践:
Keep-Alive 的实践(Tomcat 配置):
maxKeepAliveRequests:一个连接最多处理多少请求(默认 100,-1 无限)
keepAliveTimeout:长连接空闲多久关闭(默认同 connectionTimeout)
→ 平衡"复用收益"和"连接占用资源"
配置权衡:
maxKeepAliveRequests 太小 → 复用不足(频繁重建连)
太大 → 连接被长期占用
keepAliveTimeout 太长 → 空闲连接占资源
太短 → 复用不足
chunked 的实践:
① 流式响应用 chunked(Spring 的 StreamingResponseBody、SSE)
② 大文件/大数据流式输出(省内存、不用先算大小)
③ 一般框架自动处理(不知道大小就用 chunked)
现代做法:
① 用 HTTP/2(多路复用、连接复用更强、无队头阻塞)
② 流式用 SSE/WebSocket(实时推送)
③ Nginx/网关层做连接管理
核心总结:
Keep-Alive = 复用 TCP 连接(HTTP/1.1 默认),省握手、避免慢启动
chunked = 不知道总大小时分块传(每块标大小、0 块结束、支持流式)
长连接需要响应边界(Content-Length 或 chunked)才能复用
HTTP/2 多路复用更强(一个连接并发多请求)
Keep-Alive 实践(Tomcat):maxKeepAliveRequests(一个连接最多处理多少请求,默认 100)、keepAliveTimeout(空闲多久关)——平衡复用收益和连接占用(太小复用不足、太大连接被占用)。chunked 实践:流式响应用 chunked(StreamingResponseBody/SSE)、大文件/大数据流式输出、框架自动处理(不知道大小就用 chunked)。现代:用 HTTP/2(多路复用无队头阻塞)、流式用 SSE/WebSocket。理解「Keep-Alive 实践:maxKeepAliveRequests/keepAliveTimeout 平衡复用和占用;chunked 实践:流式响应/大文件/框架自动;现代用 HTTP/2 多路复用+SSE/WebSocket」,就掌握了实践。
记忆钩子:「①Keep-Alive(持久连接/长连接):一个 TCP 连接复用连续处理多个请求(不用每次建连关连),HTTP/1.0 默认短连接(要加 Connection: keep-alive)、HTTP/1.1 默认开;好处省握手+避免反复慢启动;管理 keepAliveTimeout(空闲多久关)/maxKeepAliveRequests(最多处理几个请求);局限队头阻塞(请求串行)HTTP/2 多路复用解决②分块传输 Transfer-Encoding: chunked:服务端不知道响应体总大小时(动态生成/流式/大数据边算边发无法算 Content-Length),把响应体拆成块(每块标大小十六进制+内容、0 大小块结束),支持流式;Content-Length(已知大小)vs chunked(未知)二选一;★联系:长连接复用需要知道每个响应在哪结束(响应边界),Content-Length 或 chunked 标明边界;chunked 应用:流式下载/SSE/大 JSON 流式;HTTP/2 用帧多路复用更强」。
七、常见误区与追问
- 误区:HTTP/1.1 也是每个请求新建连接。 HTTP/1.1 默认开启 Keep-Alive(长连接)——一个 TCP 连接可以复用、连续处理多个请求,不用每次新建连接;HTTP/1.0 才是默认短连接(每个请求新建、用完关,要加 Connection: keep-alive 才复用)。
- 误区:Keep-Alive 能让一个连接并发处理多个请求。 不能——HTTP/1.1 的 Keep-Alive 只是复用连接,但同一连接的请求是串行的(一个响应没回完,下一个请求要等,队头阻塞);要一个连接并发多个请求(多路复用)需要 HTTP/2。
- 误区:所有响应都要有 Content-Length。 不是——如果服务端不知道响应体总大小(动态生成、流式输出、边算边发),就用 Transfer-Encoding: chunked 分块传输(每块标大小、0 块表示结束),不用 Content-Length;Content-Length(已知大小)和 chunked(未知大小)二选一、互斥。
- 误区:chunked 只是把响应分成几块发。 chunked 的核心价值是「不用预先知道响应体总大小」——它把响应拆成块、每块前标明块大小、最后一个 0 大小的块表示结束,这样服务端可以边生成边发(流式),接收方读到 0 块就知道响应结束;用于动态生成、流式下载、SSE 等不知道总大小的场景。
- 追问:HTTP 的 Keep-Alive 是什么,为什么能提升性能? Keep-Alive(持久连接/长连接)让一个 TCP 连接建立后可以复用、连续处理多个请求(不用每个请求都新建连接、用完关闭);HTTP/1.1 默认开启;提升性能的原因:① 省去了每次请求的三次握手和四次挥手(RTT 开销),尤其一个网页有很多资源(HTML、CSS、JS、图片)时,复用一个连接加载所有资源、省大量握手;② 避免 TCP 慢启动的反复(连接热了继续用,不用每次从慢速开始)。
- 追问:分块传输编码(chunked)是干什么的,什么时候用? 当服务端不知道响应体的总大小时用——正常响应要在头里用 Content-Length 声明响应体的确切字节数,但有些情况服务端无法预先知道总大小(动态生成内容、流式输出、大文件流式下载、SSE 事件流、大 JSON 边序列化边发);这时用 Transfer-Encoding: chunked,把响应体拆成一个个块,每块前标明块大小、一块块发送、最后一个 0 大小的块表示响应结束;接收方读到 0 块就知道响应结束;这样支持流式响应、不用预先算总大小、省内存。
- 追问:Keep-Alive 和 Content-Length/chunked 有什么关系? Keep-Alive 复用连接的前提是「知道每个响应在哪结束」——一个 TCP 连接连续处理多个请求响应时,接收方必须知道上一个响应到哪结束、才能开始读下一个响应;Content-Length(声明响应体确切字节数、读够就结束)和 chunked(分块、0 块结束)都是标明响应边界的方式;有了响应边界,长连接才能正确复用(分清哪些数据属于哪个响应);短连接不需要(连接关闭就是响应结束)。
八、加强记忆
Keep-Alive(长连接)和分块传输编码(chunked)是 HTTP 的两个重要机制。① Keep-Alive(持久连接/长连接)——让一个 TCP 连接复用、连续处理多个请求(不用每次新建连接、用完关闭);HTTP/1.0 默认短连接(要加 Connection: keep-alive)、HTTP/1.1 默认开启;好处是省握手 + 避免反复慢启动(尤其网页很多资源时);管理靠 keepAliveTimeout(空闲多久关)、maxKeepAliveRequests(最多处理几个请求);局限是队头阻塞(同连接请求串行,HTTP/2 多路复用解决)。② 分块传输编码(Transfer-Encoding: chunked)——服务端不知道响应体总大小时(动态生成、流式输出、大数据边算边发、无法算 Content-Length)使用,把响应体拆成一个个「块」,每块前标明块大小、最后一个 0 大小的块表示结束,支持流式响应;Content-Length(已知大小)和 chunked(未知大小)二选一、互斥。两者的联系:长连接复用连接需要知道「每个响应在哪结束」(响应边界),Content-Length 或 chunked 都能标明边界(短连接不需要,关闭就是结束)。chunked 应用于流式下载、SSE、大 JSON 流式;HTTP/2 用二进制帧+多路复用更强(连接并发多请求)。一句话「Keep-Alive 长连接复用 TCP 连接(HTTP/1.1 默认)省握手,局限队头阻塞 HTTP/2 解决;chunked 分块传输(服务端不知道总大小时,每块标大小 0 块结束,支持流式);长连接复用需要响应边界(Content-Length 或 chunked)、二选一;chunked 用于流式下载/SSE/大 JSON」。