网络开发中的 Chunked Transfer — 它是什么、格式以及块传输的原理

作者: IT Sectr 发布日期: 2026-03-10 阅读时间: 9 分钟

Chunked Transfer — 是 HTTP 协议的一种机制,服务器通过该机制以独立的片段(块)传输响应主体,而不事先指明总数据大小。每个块包含其十六进制格式的大小和指定长度的数据,并以零大小的最终块结束。根据 MDN Web Docs, 2025Transfer-Encoding: chunked会被服务器自动启用,当响应大小事先不知时 — 例如,在实时生成内容或流式传输数据时。

要点

  • Chunked Transfer — 不事先指明 Content-Length 的情况下以部分形式传输 HTTP 响应。
  • Transfer-Encoding: chunked — 启用块传输数据模式的头。
  • 每个块包含十六进制大小、数据和结束的 CRLF,结尾由零大小的块标记。
  • 流媒体 — chunked transfer 的主要应用,用于传输音频、视频和 SSE 事件。
  • Chunked Transfer与 Content-Length 头不兼容 — 不能同时使用。

什么是 Chunked Transfer?

Chunked Transfer是 HTTP/1.1 规范(RFC 7230,第 4.1 节)中定义的一种 HTTP 机制,允许服务器在不指明总 Content-Length 的情况下以部分形式发送响应主体。服务器不在发送前计算响应大小,而是立即开始传输,数据准备好后就以片段形式发送。每个片段都附带自己的大小头,使客户端能够从片段中组装响应。

该机制由 Transfer-Encoding: chunked头启用。当客户端在响应中看到这个头时,它知道主体将以块形式传输,并应该循环读取响应:读取块大小,然后读取指定大小的数据,然后重复。当遇到零大小的块时,过程结束。Chunked Transfer 是 HTTP/1.1 的必要组成部分,所有现代 Web 服务器和 HTTP 客户端都支持它。

使用 chunked transfer 的主要原因是 动态内容生成。当服务器根据数据库查询、外部 API 或长时间计算生成响应时,它无泔事先知道结果的大小。不将整个响应缓冲在内存中(这对大容量数据有风险),服务器启用 Transfer-Encoding: chunked 并在数据准备好时发送。这对于内存有限的服务器以及大小可能非常大的响应尤其重要 — 从 100 MB 以上。

HTTP/1.1 chunked 与 HTTP/2 的区别

在 HTTP/2 中,chunked transfer 机制作为一个独立功能不存在,因为该协议在帧级别上使用流的多路复用。在 HTTP/2 中,任何大小的数据都在 DATA 帧中传输,响应主体的大小不需要事先声明 — 流可以随时关闭。现代服务器在代理到 HTTP/2 upstream 时,会自动将 HTTP/1.1 chunked 响应转换为等效的流式传输。Chunked Transfer 对于 HTTP/1.1 连接仍然有效。

块传输如何工作

当服务器决定使用 Chunked Transfer 时,它不计算 Content-Length,而是发送 Transfer-Encoding: chunked 头。然后,响应主体作为一系列块形成。每个块以一行包含十六进制格式(无 0x 前缀)的块大小开始,后跟 CRLF( )。然后是指定大小的块数据,以 CRLF 结束。最后一个块的大小为 0,之后可以有 trailer 头。

十六进制大小允许传输任何大小的块 — 从 1 字节到理论上无限的体积。在实践中,块大小由服务器选择:典型值为 4 KB、8 KB 或 16 KB。最佳块大小是 TCP 段大小(对于以太网通常为 1460 字节)的倍数,以最小化传输层的碎片化。Nginx 默认使用 4 KB 块,Apache 使用 8 KB 块。

接收到 Transfer-Encoding: chunked 的客户端必须逐块读取响应,直到最终的零块。如果客户端不支持 chunked transfer,服务器就不能使用这种模式。在实践中,所有现代 HTTP 客户端 — 浏览器、OkHttp、URLSession、curl — 都完全支持 chunked 响应。流式读取允许客户端在收到完整响应之前就开始处理数据,这对性能至关重要。

块元素格式示例
块大小HEX + CRLF1000
块数据[大小字节] + CRLF[4096 字节数据]
结束块0 0
Trailer(可选)头 + CRLFExpires: Wed, 21 Oct 2025

Chunked Transfer 中的 Trailer 头

Chunked Transfer 支持 trailer 头 — 在最后一个块之后传输的额外 HTTP 头。这对于只在响应生成完成后才知道的元数据很有用:例如,Content-MD5X-Compression-Ratio。Trailer 头必须在 Trailer 头中声明:Content-MD5, X-Compression-Ratio。在实践中,trailer 很少使用 — 多数服务器不将它们包含在响应中。

Chunked 响应的格式

Chunked 响应具有严格定义的结构,客户端必须正确解析它。让我们看一个带 Transfer-Encoding: chunked 的 HTTP 响应完整示例。在头和空行之后,响应主体开始。主体结构是一个序列:块大小 数据 块大小 数据 ... 直到 0 。每个大小使用 ASCII 字符以十六进制数字系统传输。

带 Chunked Transfer 的服务器响应示例:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

7
Hello
6
World!
0

在这个示例中,服务器在两个块中传输字符串 “Hello World!”。第一个大小为 7 字节的块包含 “Hello ”,第二个块为 6 字节的 “World!”。客户端从两个块中收集数据并获得完整字符串。重要:块大小仅包含数据,不包含块本身的 CRLF 分隔符。最后的空块(0 )告诉客户端传输已结束。

kotlin
import java.net.HttpURLConnection
import java.io.BufferedReader
import java.io.InputStreamReader

fun readChunkedResponse() {
    val url = java.net.URL("https://stream.example.com/data")
    val connection = url.openConnection() as HttpURLConnection
    val reader = BufferedReader(
        InputStreamReader(connection.inputStream)
    )

    var line: String?
    while (reader.readLine().also { line = it } != null) {
        println("Chunk: $line")
    }
    reader.close()
}

在 OkHttp 中解析 chunked 响应

OkHttp 将程序员完全抽象出 Chunked Transfer 的细节。当收到带 Transfer-Encoding: chunked 的响应时,OkHttp 自动收集块并通过 response.body?.string() 向程序员提供完整的响应主体。对于流式处理,使用 response.body?.source(),它返回一个 BufferedSource并允许在数据到达时读取数据。程序员不需要手动解析十六进制大小和 CRLF — 库会自动完成这些操作。

Chunked Transfer 与 Content-Length 的对比

Content-Length 和 Transfer-Encoding: chunked 是指定 HTTP 消息主体大小的两种互斥方法。Content-Length是一个包含主体精确字节大小的头。它对于大小事先已知的响应以及带主体的请求(POST、PUT)是必须的。Content-Length 允许客户端事先分配合适大小的缓冲并检查是否已接收到所有数据。

Chunked Transfer 在主体大小事先不知时使用。这发生在三种主要场景中:动态内容生成(例如,结果尚未收到的数据库查询)、大文件的流式传输(以免将整个文件缓冲在内存中)以及用于实时事件传输的 Server-Sent Events (SSE)。选择Content-Length 和 chunked 之间的选择是服务器的责任。如果服务器在传输开始前知道大小,应该使用 Content-Length 作为更简单、更可预测的机制。

HTTP/1.1 规范禁止同时使用 Content-Length 和 Transfer-Encoding: chunked。如果服务器发送了两个头,客户端必须忽略 Content-Length 并将响应作为 chunked 处理。Transfer-Encoding 的优先级高于 Content-Length 是在 RFC 7230 中规定的,用于代理服务器修改响应主体时无泔保持原始 Content-Length 的情况。一些旧的 HTTP 客户端不能正确处理这种情况,但现代实现遵循规范。

Content-Length 不可能的情况

有一些场景中,Content-Length 原则上无泔事先计算。动态报告根据用户请求以筛选和聚合方式生成 — 服务器在数据库查询完成之前不知道数据体积。从摄像头实时传输的流式视频 — 大小是无限的。用于通知的 SSE 和 long polling — 响应可能无限期持续。在所有这些情况下,Chunked Transfer 是唯一正确的机制。

基于 chunked transfer 的流媒体

Chunked Transfer 是网上许多流媒体技术的基础。最著名的是 Server-Sent Events (SSE),服务器通过一个带 Transfer-Encoding: chunked 的 HTTP 连接向客户端发送事件。SSE 使用特殊的文本格式(data: 消息 ),但传输层是普通的 chunked transfer。浏览器在服务器发送事件时立即接收,不等待响应结束。

音频和视频流媒体也依赖于 Chunked Transfer。像 Nginx RTMP和 Wowza Streaming Engine 这样的媒体服务器通过 HTTP 以块形式发送媒体数据。客户端的播放器在收到第一个块时开始播放,不等待文件完全加载。这将播放开始时间(Time to First Frame)从几十秒缩短到 1-2 秒。YouTube 和 Netflix 正是使用这种方法进行他们的 HTTP 流媒体传输。

在移动应用开发中,Chunked Transfer 用于传输大量数据而不将整个响应加载到内存中。在 Android 上通过 Coil或 Glide 加载图片时,库以块形式读取流式数据并逐步解码图像。这可以显示大图片(10+ MB)而不会出现 OutOfMemoryError。OkHttp 通过 response.body?.byteStream() 支持流式读取,它返回一个 InputStream,逐块读取数据。

gRPC 和 GraphQL 中的 Chunked transfer

gRPC 使用 HTTP/2,其中流式传输嵌入在协议层,不需要单独的 chunked 机制。通过 HTTP/1.1 工作的 GraphQL 服务器可以使用 Chunked Transfer来流式传输订阅结果(subscriptions)。Apollo Server 和 Hasura 为 GraphQL 订阅发送 chunked 响应,在事件发生时传输事件。客户端无需轮询即可接收实时更新。

优点和局限性

Chunked Transfer 为 Web 应用程序提供了重要优势。立即发送数据 — 服务器在发送前不缓冲响应,减少了到第一个字节的延迟。流式处理 — 客户端可以在数据到达时就开始处理,无需等待完全加载。无内存限制 — 服务器不将完整响应存储在内存中,这对于大数据量至关重要。传输无限流的能力 — SSE、实时视频、监控。

然而,Chunked Transfer 也有局限性。每个块的额外开销为 6-12 字节(大小 + CRLF),对于大量小块(例如 100 字节)可能使响应大小增加 10-15%。无泔指明精确大小 — 客户端无泔事先分配缓冲或显示进度条。代理服务器问题 — 一些旧的代理不支持 chunked transfer,无泔缓存这类响应。继续下载缺乏支持 — 对于部分接收的 chunked 响应,无泔发起 Range 请求。

根据 HTTP Archive, 2025,约有 35% 的所有 HTTP 响应使用 Transfer-Encoding: chunked。其中主要是动态页面(60%)、API 响应(25%)和媒体流(15%)。静态文件几乎始终使用 Content-Length,因为它们的大小事先已知。随着 HTTP/2 的普及,chunked 响应的比例逐渐下降,因为在 HTTP/2 中,流式传输在帧级别实现,不需要额外的 Transfer-Encoding 头。

实用建议

在移动应用开发中,使用 Chunked Transfer 加载大文件(图片、视频)以及返回大型数据数组的 API 请求。OkHttp 无需额外配置即可完全支持 chunked transfer。对于 上传到服务器(upload),Chunked Transfer 不适用 — Transfer-Encoding 不用于 HTTP/1.1 中的上传。在 iOS 上,URLSession 无需特殊配置即可支持发送和接收 chunked 数据。流式 JSON 解析(例如,通过 Jackson Streaming API 或 Moshi)可以在 chunked 流到达时处理大型 JSON 数组。

常见问题

服务器如何启用 Chunked Transfer?

当响应大小未知时,服务器会自动启用 Chunked Transfer。如果没有设置 Content-Length,Nginx 会添加 Transfer-Encoding: chunked。在 Spring Boot 中,StreamingResponseBody 和 SseEmitter 自动使用 chunked transfer。在 Node.js Express 中,如果在没有 Content-Length 的情况下调用 res.write() 和 res.end(),响应将成为 chunked。

可以同时使用 Content-Length 和 chunked 吗?

不能,HTTP/1.1 规范禁止同时使用 Content-Length 和 Transfer-Encoding: chunked。如果服务器发送了两个头,客户端必须忽略 Content-Length 并将响应作为 chunked 处理。这一规则在 RFC 7230 中规定,以便与可能修改响应主体的代理服务器兼容。

什么是最佳的块大小?

最佳块大小取决于场景。对于普通网页 — 4-8 KB。对于视频流媒体 — 16-64 KB。对于 SSE — 最小块为 1-2 KB,以减少延迟。块大小应该是 TCP 段大小(对于以太网为 1460 字节)的倍数,以最小化传输层的碎片化。

Chunked Transfer 可以通过代理工作吗?

现代代理服务器(Nginx、HAProxy、Envoy)支持 Chunked Transfer。代理可以不缓冲地传输块(流媒体),或将整个响应缓冲并使用 Content-Length 重新发送。旧的代理可能会将 chunked 响应缓冲到结束,这会增加延迟。HTTP/2 在协议层解决了这个问题。

Chunked Transfer 与 HTTP chunked encoding 有什么区别?

它们是同一回事。Chunked Transfer是来自 HTTP/1.1 规范的完整名称。HTTP chunked encoding 是同一个概念,有时在库文档中使用。Transfer-Encoding: chunked 是启用这种模式的头。所有三个术语都描述了同一种以部分形式传输数据的机制。

总结

  • Chunked Transfer — HTTP/1.1 机制,不事先指明 Content-Length 的情况下以块形式传输响应主体。
  • Transfer-Encoding: chunked — 启用块传输的头;每个块包含十六进制大小、数据和 CRLF。
  • 零大小的结束块标志着传输的结束,之后可以有 trailer 头。
  • 动态内容流媒体 — 主要应用:动态页面、SSE、流式音频/视频、长报告。
  • Content-Length 和 chunked 互斥 — 规范禁止同时使用这些头。
  • 优点 — 减少延迟、节省内存、传输无限流的能力以及客户端的流式处理。
  • 局限性 — 块头的额外开销、无泔显示进度、与旧代理服务器的问题。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读