Skip to content

断点续传核心原理精讲

1. 核心思想与重要性

1.1 为什么需要断点续传?

在处理大文件(视频、软件包等)的上传或下载时,网络中断、设备死机等异常情况会导致传输失败。如果没有断点续传机制,任务必须从头开始,极大地浪费了时间和带宽。

核心目标:允许用户在传输中断后,从上次失败的地方继续,而不是从零开始。

学习重点:本笔记的重心在于理解断点续传的工作原理,而非精通具体的代码实现。原理是通用的,而代码实现会因业务场景和技术栈而异。

1.2 两种模式:下载与上传

断点续传分为断点下载断点上传,二者原理相近但实现方式和责任方不同。

  • 断点下载:客户端从服务器一段一段地拉取文件数据,并在本地组装。
  • 断点上传:客户端将文件切分成多个片段,一段一段地发送给服务器,由服务器进行组装。

2. 断点下载 (Breakpoint Download)

对于前端开发者而言,断点下载主要了解其原理即可,因为浏览器和常见的下载工具(如迅雷)已经内置了客户端逻辑,我们无法通过页面JS直接操作本地临时文件来手动实现。

2.1 原理概述

断点下载的实现依赖于客户端和服务器之间通过特定的 HTTP 头部信息进行“协商”。

核心流程

  1. 客户端先“询问”文件信息。
  2. 客户端请求文件的某一个片段(Range)。
  3. 服务器返回该片段数据。
  4. 客户端将所有片段数据保存并最终合并成完整文件。

2.2 关键 HTTP 头部

  1. Accept-Ranges: bytes (响应头)

    • 服务器通过这个响应头告知客户端:“我支持断点续传,并且单位是字节(bytes)”。这是实现断点下载的前提。
  2. Content-Disposition: attachment; filename="xxx" (响应头)

    • 一个标准的下载响应头,告诉浏览器这是一个附件,应触发下载行为。
  3. Range: bytes=start-end (请求头)

    • 客户端使用此请求头告诉服务器,我这次只需要文件中从 start 字节到 end 字节范围的数据。
  4. 206 Partial Content (状态码)

    • 当服务器成功处理了 Range 请求并返回部分数据时,会使用此状态码,而不是 200 OK

2.3 详细工作流程

  1. (可选) 发送 HEAD 请求

    • 专业的下载工具首先会发送一个 HEAD 请求。此请求只获取响应头,不获取响应体。
    • 目的:在不下载任何实际内容的情况下,提前获取文件的大小 (Content-Length)、文件名等元信息,并确认服务器是否支持断点续传 (Accept-Ranges: bytes)。
  2. 分段 GET 请求

    • 客户端(如下载工具)根据文件总大小决定如何分段。为了提高效率,可能会并发地发送多个请求,每个请求负责下载文件的一个不同部分。
    • 示例:请求文件的前 5001 个字节。
    http
    GET /some-large-file.zip HTTP/1.1
    Host: example.com
    Range: bytes=0-5000
  3. 服务器响应

    • 服务器收到带 Range 头的请求后,返回 206 Partial Content 状态码,并在响应体中只包含 0-5000 字节的数据。
    • 响应头中会包含 Content-Range 来指明返回的数据范围,以及 Content-Length 来指明这部分数据的大小。
  4. 客户端处理

    • 客户端接收到这些数据片段,将它们暂存到本地的临时文件中。
    • 重复步骤 2 和 3,直到下载完所有分片。
    • 所有分片下载完成后,客户端将临时文件中的数据按正确顺序合并,组成一个完整的、可用的文件。

2.4 如何“续传”?

如果传输过程中断,客户端已经保存了部分下载的临时文件。当用户再次开始任务时:

  1. 客户端检查本地临时文件,得知已经下载到了哪个字节(例如,已完成 0-5000)。
  2. 它会直接从下一个字节开始请求,即发送 Range: bytes=5001-xxxx 的请求。
  3. 这样就跳过了已下载的部分,实现了“断点续传”。

3. 断点上传 (Breakpoint Upload)

断点上传的逻辑与下载相反,且对前端开发者来说更为重要,因为页面常常需要支持大文件上传功能。

3.1 原理概述

与下载不同,HTTP 并没有为断点上传规定一套统一的标准协议。因此,开发者需要自行设计客户端与服务器之间的交互流程。

核心思想

  1. 客户端:负责将大文件切片 (Slice / Chunk)
  2. 服务器:负责接收这些切片,并最终将它们合并 (Merge) 成完整文件。

为什么更复杂?

  • 无统一标准:需要自行定义接口、参数和流程。
  • 客户端责任重:文件切片、计算文件指纹(MD5)、管理上传状态等逻辑都需要在前端实现。
  • 服务器责任重:需要实现接收切片、校验、暂存、合并、清理临时文件等一系列复杂逻辑。

3.2 关键技术:文件指纹 (MD5)

MD5 (或其它 Hash 算法) 是实现断点上传的基石,它是一种文件指纹技术。

  • 特性
    1. 任何长度的内容都能生成一个固定长度的字符串。
    2. 相同的内容生成的 MD5 值一定相同。
    3. 内容有任何微小变化,生成的 MD5 值都会有巨大差异。
  • 作用
    • 唯一标识文件:使用整个文件的 MD5 值作为其唯一 ID。
    • 唯一标识分片:使用每个分片的 MD5 值作为该分片的唯一 ID。
    • 校验完整性:通过对比 MD5 值可以判断文件或分片在传输过程中是否损坏。

3.3 详细工作流程 (一种常见的实现方案)

Step 1: 客户端 - 文件预处理

当用户选择一个文件后,前端 JS 需要执行以下操作:

  1. 文件切片 (Slicing):使用 File.prototype.slice() 方法将文件按固定大小(如 2MB)切成多个二进制数据块 (Blob)。
  2. 计算 MD5
    • 计算整个文件的 MD5 值。
    • 计算每一个文件切片的 MD5 值。

    注意:计算 MD5 是一个耗时操作,尤其对于大文件。为了避免页面卡顿,通常会使用 Web Worker 在后台线程中进行计算。

Step 2: 客户端与服务器 - 上传前协商

在真正上传文件数据前,客户端先向服务器发送一个“协商”请求,告知服务器文件的基本信息。

  • 接口 (示例): POST /api/upload/handshake

  • 请求体 (JSON):

    json
    {
      "fileId": "d41d8cd98f00b204e9800998ecf8427e", // 整个文件的 MD5
      "ext": ".mp4", // 文件后缀名
      "chunkIds": [ // 所有分片的 MD5 列表
        "9e107d9d372bb6826bd81d3542a419d6",
        "1f3870be274f6c49b3e31a0c6728957f",
        "..."
      ]
    }
  • 服务器处理

    1. 检查 fileId (文件 MD5),判断该文件是否已经上传过 (秒传)。
      • 如果是,直接返回文件地址,上传结束。
    2. 如果文件未完全上传,则检查 chunkIds,对比服务器上已存在的临时分片,找出哪些分片还未上传
    3. 响应:返回需要上传的分片 ID 列表。
    json
    // 示例:服务器发现第一个分片已存在,告诉客户端只需上传第二个
    {
      "neededChunkIds": [
        "1f3870be274f6c49b3e31a0c6728957f",
        "..."
      ]
    }

Step 3: 客户端 - 上传分片

客户端根据服务器返回的 neededChunkIds 列表,开始上传缺失的分片。

  • 接口 (示例): POST /api/upload/chunk

  • 请求体 (FormData):

    // 使用 FormData 来同时传递二进制数据和元数据
    - file: [Blob]      // 分片的二进制数据
    - chunkId: "..."   // 当前分片的 MD5
    - fileId: "..."    // 所属文件的 MD5
  • 客户端可以一个接一个地串行上传,也可以开启多个请求并行上传以提高速度。每上传成功一个分片,服务器就返回最新的还需上传的分片列表,客户端据此更新进度条。

Step 4: 服务器 - 合并分片

当服务器接收到属于同一个 fileId 的所有分片后,会触发合并操作:

  1. 将所有临时分片文件按正确的顺序(可以通过分片 MD5 列表或自定义的索引来保证)读出。
  2. 将它们的内容写入一个新文件。
  3. 合并成功后,删除所有临时分片。
  4. 此时,服务器可以通知客户端上传已全部完成,并返回最终的文件访问地址。

3.4 如何“续传”?

如果上传过程中断(如刷新页面),当用户再次选择同一个文件时:

  1. 前端 JS 重新执行 Step 1(切片和计算 MD5)。因为文件内容和切片算法不变,所以得到的 fileIdchunkIds 与中断前完全一样。
  2. 前端再次执行 Step 2(上传前协商)。
  3. 服务器根据已保存在服务端的临时分片,会准确地返回剩余未上传的分片列表。
  4. 客户端从 Step 3 开始,只上传这些缺失的分片,从而实现了“断点续传”。

4. 总结 & 面试要点

当面试官问及“断点续传”时,清晰地阐述其原理是关键。

回答框架

  1. 阐明问题:首先说明断点续传是为了解决大文件传输中因网络中断等问题导致需要重传的痛点。

  2. 分两种情况讨论:下载和上传。

  3. 讲解断点下载

    • 核心:依赖服务器和客户端(浏览器/下载工具)共同支持。
    • 服务器:需要通过 Accept-Ranges: bytes 响应头声明支持。
    • 客户端:通过发送带 Range 请求头的 GET 请求,只请求文件的特定字节范围。
    • 服务器响应:返回 206 Partial Content 状态码和部分数据。
    • 续传:客户端记录已下载的字节位置,中断后从该位置继续请求。
  4. 讲解断点上传

    • 核心:由于无统一标准,需要自行设计协议,关键在于“分片上传、服务端合并”。
    • 客户端
      1. 文件切片:使用 File.slice
      2. 计算指纹:为整个文件和每个分片计算 MD5,作为唯一标识。
      3. 上传前协商:将文件和分片的 MD5 信息发给服务器,询问哪些分片需要上传(实现秒传和续传的关键)。
      4. 上传分片:根据服务器的响应,上传缺失的分片。
    • 服务器
      1. 接收并校验分片。
      2. 暂存分片。
      3. 当所有分片接收完毕后,合并成完整文件。
    • 续传:中断后,通过“协商”步骤,服务器能告诉客户端哪些分片已存在,客户端只需上传剩余部分。