
简介这份资源面向Linux网络编程学习者与需要实现大文件传输的开发者聚焦断点续传与多线程下载两项核心技术帮助理解如何在网络不稳定场景下提升下载效率与用户体验。包内共4个文件以2个cc源文件、1个h头文件和1个txt说明为主压缩包约3KB体量轻巧但结构完整套接字类封装负责网络连接管理HTTP主程序承载断点续传与多线程下载逻辑头文件定义接口文本文件则提供来源与使用说明。已有190人学习下载适合作为网络编程课程设计或练手项目的参考。读者可从中获取HTTP HEAD获取文件信息、分块下载、线程同步与合并、进度保存与恢复、错误处理等关键实现思路并对照源码理解套接字通信与并发控制的落地方式为自行实现高效下载工具提供可复用的代码骨架与排错参考。1. 从 download.rar 说起Linux 断点续传与多线程下载到底解决什么问题你大概率遇到过这种场景从一台海外镜像站拉一个 8GB 的 Linux 发行版 ISO宿舍或办公室的网每隔十几分钟抖一次wget 跑到 60% 断了重来一遍又从 0% 开始。或者你在运维一台只有 1Mbps 出口的云主机要从对象存储同步一个 20GB 的数据库备份单连接跑满带宽也要跑一整天。download.rar这个标题背后其实是一类非常具体的工程需求在 Linux 上把「大文件下载」这件事做得可中断、可恢复、可并行。断点续传解决的是「断了不用从头来」多线程下载解决的是「单连接跑不满带宽」。两者组合起来才是生产环境里真正能用的下载方案。这篇笔记面向的是需要在 Linux 上稳定拉大文件的运维、后端和嵌入式开发者从 HTTP Range 协议讲到 aria2、axel、wget 的实际配置再到自己写一个可控的 Python 多线程下载器把参数、坑和验证方法都摊开讲。2. 断点续传的底层依据HTTP Range 与文件分片怎么对上2.1 断点续传不是「记住进度」那么简单很多人对断点续传的理解停留在「下载工具记了个进度条」。实际上断点续传能成立的前提是服务端支持HTTP Range 请求。客户端在请求头里带上Range: bytes1024000-意思是「从第 1024000 字节开始给我后面的内容」服务端如果支持会返回206 Partial Content而不是200 OK并在响应头里给出Content-Range: bytes 1024000-10485759/10485760。客户端拿到这段数据后用追加模式写进本地文件就完成了「续」。这里有个容易被忽略的点断点续传的「断点」必须和本地文件的实际大小严格对齐。如果你本地文件已经有 1024000 字节但请求时写的是Range: bytes1000000-那这 24000 字节就会重复写入文件直接损坏。所以正确的做法是每次续传前先stat本地文件拿到真实大小再拿这个值去拼 Range 头。这也是为什么很多下载工具在续传前会做一次 HEAD 请求确认远端文件的总大小和Accept-Ranges字段。判断服务端是否支持断点续传最直接的方式是发一个 HEAD 请求看响应头curl -I -H Range: bytes0-1 https://example.com/ubuntu-24.04.iso如果返回里出现Accept-Ranges: bytes和206说明支持如果只有200且没有Accept-Ranges那这个源就不支持断点续传任何工具都救不了只能换源。这是排查「为什么我的下载器续传失败」的第一步别急着怀疑工具。2.2 多线程下载的本质是把一个文件切成多个 Range多线程下载并不是「开多个连接下载同一个文件」这么模糊。它的准确做法是先通过 HEAD 拿到文件总大小total然后按线程数N把[0, total)切成 N 段每个线程负责一段各自发Range: bytesstart-end请求最后把 N 个分片按顺序合并。比如 100MB 文件、4 线程每段 25MB线程 0 请求bytes0-26214399线程 1 请求bytes26214400-52428799以此类推。这里的关键参数是分片大小和线程数。分片太小请求头开销占比高而且服务端可能限流分片太大单线程跑太久失去并行意义。经验值是单分片不小于 1MB线程数控制在 4 到 16 之间。超过 16 线程大多数服务端会触发并发连接限制反而变慢甚至被封 IP。另外要注意多线程下载和断点续传是正交的两件事多线程解决速度断点续传解决可靠性。一个成熟方案应该同时支持——每个分片自己记录已下载的偏移量中断后从各自断点继续而不是整个文件重来。2.3 用 aria2 跑通第一个多线程断点续传任务aria2 是 Linux 上最省心的选择一条命令同时搞定多线程和断点续传aria2c \ -x 8 \ # 单服务器最大连接数即线程数 -s 8 \ # 分片数通常和 -x 保持一致 -k 1M \ # 每个分片最小 1MB -c \ # 开启断点续传 --file-allocationnone \ # 不预分配避免大文件卡顿 -d /data/downloads \ # 下载目录 -o ubuntu.iso \ # 保存文件名 https://mirrors.example.com/ubuntu-24.04.iso-x和-s是最常调的两个参数。-x控制对同一服务器的并发连接数-s控制文件被切成几片。两者设成一样最直观。-k是分片大小默认 20M对小文件偏大改成 1M 更灵活。-c是续传开关aria2 会在下载目录生成一个.aria2控制文件记录每个分片的进度下次执行同样的命令会自动读取它继续。注意不要手动删除.aria2文件删了就等于放弃断点只能从头来。如果下载完成aria2 会自动清理这个控制文件。--file-allocationnone这个参数值得单独说。默认情况下 aria2 会预分配磁盘空间falloc 或 trunc对 20GB 的文件在某些文件系统上会卡住几十秒甚至报错。设成none就不预分配边下边写。代价是磁盘碎片可能多一点但对下载场景完全可接受。3. 自己写一个可控的多线程断点续传下载器3.1 为什么还要自己写aria2 和 axel 已经很好用了但有些场景你必须自己控制比如要在下载过程中做鉴权刷新、要把分片写到不同的存储、要对接自己的任务调度系统、要在内网环境里去掉外部依赖。这时候一个 200 行左右的 Python 下载器比调参更靠谱。下面这个实现覆盖了 HEAD 探测、分片切分、多线程 Range 请求、断点记录和合并核心逻辑都在注释里。3.2 核心代码分片、续传与合并import os import threading import requests class MultiThreadDownloader: def __init__(self, url, filepath, threads8, chunk_size1024*1024): self.url url self.filepath filepath self.threads threads self.chunk_size chunk_size self.total_size 0 self.lock threading.Lock() def probe(self): # HEAD 探测总大小和是否支持 Range resp requests.head(self.url, allow_redirectsTrue, timeout10) self.total_size int(resp.headers.get(Content-Length, 0)) accept_ranges resp.headers.get(Accept-Ranges, ) if accept_ranges ! bytes: raise RuntimeError(服务端不支持 Range无法断点续传) return self.total_size def download_part(self, index, start, end): part_file f{self.filepath}.part{index} # 读取已有分片大小实现分片级断点续传 existing os.path.getsize(part_file) if os.path.exists(part_file) else 0 if existing (end - start 1): return # 该分片已完成 headers {Range: fbytes{start existing}-{end}} mode ab if existing else wb with requests.get(self.url, headersheaders, streamTrue, timeout30) as r: r.raise_for_status() with open(part_file, mode) as f: for chunk in r.iter_content(chunk_size64*1024): if chunk: f.write(chunk) def run(self): self.probe() part_size self.total_size // self.threads ranges [] for i in range(self.threads): start i * part_size end self.total_size - 1 if i self.threads - 1 else (start part_size - 1) ranges.append((i, start, end)) threads [] for i, start, end in ranges: t threading.Thread(targetself.download_part, args(i, start, end)) t.start() threads.append(t) for t in threads: t.join() self.merge() def merge(self): with open(self.filepath, wb) as out: for i in range(self.threads): part_file f{self.filepath}.part{i} with open(part_file, rb) as pf: while True: buf pf.read(1024*1024) if not buf: break out.write(buf) os.remove(part_file) if __name__ __main__: d MultiThreadDownloader( urlhttps://mirrors.example.com/ubuntu-24.04.iso, filepath/data/downloads/ubuntu.iso, threads8 ) d.run()逻辑上分四步probe用 HEAD 拿总大小并校验Accept-Rangesrun按线程数切分区间download_part每个线程独立请求自己的 Range并且先检查本地分片文件已有多少字节从已有偏移继续这就是分片级断点续传merge按序号顺序拼接分片并删除临时文件。参数上threads建议 4 到 16chunk_size是写入缓冲区大小64KB 到 1MB 都行太小会增加系统调用次数太大占内存。timeout30是连接和读取超时内网可以调小公网建议保留。3.3 断点记录该存哪里上面的实现把断点信息隐含在.partN文件的大小里简单可靠但有个前提分片区间在每次运行时必须完全一致。如果第二次运行时threads从 8 改成 4分片边界就变了.part文件对不上续传会出错。生产环境更稳妥的做法是额外写一个 JSON 元数据文件记录total_size、threads、每个分片的start/end/downloaded续传前先读元数据校验一致性。如果发现参数变了要么按旧参数继续要么提示用户清理重下。这个元数据文件就是你的「后悔药」别省这一步。4. 避坑与排查断点续传和多线程下载最容易翻车的地方4.1 续传后文件校验失败md5 对不上现象下载完成但md5sum和官方值不一致文件打不开或解压报错。原因最常见的是分片重叠或缺失。比如某个线程请求的 Range 起点算错或者服务端返回的Content-Range和请求的不一致但客户端没校验导致某段数据写了两遍或漏了一段。解决在download_part里校验响应头Content-Range的起点是否等于请求起点不等就抛异常重试。合并完成后如果源站提供校验值务必跑一次md5sum或sha256sum对比。别嫌麻烦大文件下载最怕的就是「看起来完成了但内容是坏的」。4.2 线程数拉满反而更慢甚至被限流现象把-x设成 32 甚至 64速度不升反降或者跑一会儿全部连接超时。原因服务端或中间网关对单 IP 的并发连接数有限制超过阈值会触发限流甚至临时封禁。另外线程太多会导致磁盘随机写入加剧机械盘上尤其明显。解决线程数从 4 开始试逐步加到 8、16观察速度曲线。大多数场景 8 线程已经能跑满百兆带宽。如果服务端明确限制并发就老实降到 2 到 4。记住多线程下载的收益来自「单连接跑不满带宽」如果单连接已经跑满加线程没有任何意义。4.3 磁盘空间不足导致续传文件损坏现象下载到一半报No space left on device清理空间后重新运行续传失败或文件损坏。原因磁盘写满时分片文件可能只写了一半但文件系统记录的 size 和实际有效数据不一致。更糟的是某些文件系统在空间不足时会截断写入。解决下载前用df -h确认目标分区剩余空间大于文件总大小最好留 10% 余量。如果已经写满不要直接续传先检查各.part文件大小是否合理必要时删掉最后那个可能损坏的分片重下。aria2 的--file-allocationfalloc能在开始时就占住空间避免下到一半才发现不够但会牺牲启动速度按需选择。4.4 重定向后 Range 请求失效现象URL 经过 301/302 跳转后续传请求返回200而不是206或者返回的内容不是从指定偏移开始。原因部分服务端在重定向后不保留 Range 语义或者跳转到了不支持 Range 的 CDN 节点。解决用curl -I -L跟踪完整跳转链拿到最终 URL 后直接对最终地址发 Range 请求。代码里requests.head(allow_redirectsTrue)拿到的是最终响应头但后续 GET 如果还用原始 URL可能每次跳转行为不一致。稳妥做法是探测阶段就解析出最终 URL后续所有请求都用它。4.5 断点文件被误删或跨机器不通用现象换了台机器继续下载发现断点信息没了只能从头来。原因断点信息存在本地.aria2或.part文件里没有随文件一起迁移。解决如果要跨机器续传把控制文件和数据文件一起拷贝并确保目标路径一致。自己写的下载器可以把元数据存成 JSON 放在文件同目录迁移时一起带走。另外注意不同工具的控制文件格式不通用aria2 的.aria2不能被 axel 识别别混用。5. 进阶技巧把下载器接进自动化流水线并验证完整性5.1 用校验和与分段验证兜底下载完成后只跑一次全文件 md5 是基本操作但对超大文件全量校验本身也要几分钟。更高效的做法是分段校验如果源站提供分片校验值有些镜像站会提供.sha256分片清单可以在每个分片下载完就校验发现问题立即重下该分片而不是等整个文件合并后才发现。自己写下载器时可以在download_part结束后对该分片算一次 sha256和预期值比对。没有分片校验值就退而求其次合并后全量校验。5.2 把下载任务做成可重入的脚本生产环境里下载往往不是手动执行而是由定时任务或 CI 触发。这时候脚本必须可重入重复执行不会破坏已有进度也不会重复下载已完成的部分。下面这个 bash 封装是一个我常用的模板#!/bin/bash set -euo pipefail URL$1 OUT$2 THREADS${3:-8} # 已存在且校验通过则直接退出实现幂等 if [ -f $OUT ]; then echo 文件已存在跳过下载 exit 0 fi aria2c -x $THREADS -s $THREADS -k 1M -c \ --file-allocationnone \ --max-tries5 \ --retry-wait10 \ -d $(dirname $OUT) \ -o $(basename $OUT) \ $URL # 下载后校验失败则删除文件让下次重来 if ! md5sum -c ${OUT}.md5 2/dev/null; then echo 校验失败清理文件 rm -f $OUT exit 1 fi关键点是--max-tries5 --retry-wait10让 aria2 在遇到临时网络错误时自动重试而不是直接失败退出。set -euo pipefail保证任何一步出错脚本就停不会带着坏文件继续往下走。校验失败时删除文件下次执行会重新下载避免坏文件被当成「已完成」跳过。5.3 监控下载进度和速度的实用命令aria2 默认输出比较啰嗦可以用--summary-interval10控制摘要输出频率或者加--console-log-levelwarn只看警告。如果想在脚本里拿到进度可以解析 aria2 的--show-console-readout输出或者干脆用watch -n 5 ls -l /data/downloads/*.part*看分片文件增长。对于自己写的下载器建议每个分片线程定期打印已下载字节数方便判断是否卡死。一个简单的判断标准如果 30 秒内所有分片大小都没变化大概率是连接挂了需要超时重连机制。我自己的习惯是任何超过 1GB 的下载任务都必须带断点续传和校验且脚本要能重复执行。早年吃过一次亏一个 15GB 的备份文件下载完没校验结果解压时报错重新下载又花了 6 小时。从那以后md5sum -c成了我所有下载脚本的标配。希望这些经验能帮到你少走点弯路。本文还有配套的精品资源点击获取