简介:这份RAR压缩包聚焦Linux环境下断点续传与多线程下载的实现,面向网络编程学习者、C++开发者以及需要在大文件传输场景中优化下载效率的运维或后端人员。包内共4个文件,以cc源码为主,另有1个h头文件与1个txt说明文档,整体仅3KB,属于轻量级示例代码。其中CSocket.cc与CSocket.h构成基础的套接字封装,负责网络连接的建立与管理;http_main.cc则是主程序入口,集中体现了HTTP断点续传请求与多线程分块下载的核心逻辑;txt文件用于补充程序来源或使用说明。已有189人学习下载。压缩包虽然小巧,但代码结构清晰,完整覆盖初始化连接、获取文件信息、多线程分块、断点续传、错误处理等关键环节,适合想理解底层网络编程实现细节的读者拆解与复用。
1. 一张 download.rar 背后的硬需求:Linux 大文件下载为什么非断点续传不可
我见过最多的场景是这样的:你在 Linux 服务器上敲下 wget,开始拉一个 4GB 的 Linux 镜像,等到第 2 个小时,SSH 断了,整个下载直接灰飞烟灭。再登录上去,看到的还是一个 0 字节的空文件。标题里的 download.rar 可以理解为任何一个你想要的资源包,但比压缩包本身更关键的,是下载过程能不能扛住断线、能不能把单线程变成多线程并行。
这篇笔记围绕 Linux 断点续传与多线程下载这条主线展开,从 HTTP Range 原理讲起,落到 wget、curl、aria2 三款工具的选型,再给出一套可以直接抄作业的下载脚本和排查清单。适合经常在服务器上拉取镜像、数据集、固件包或备份文件的工程师。如果你也有过半夜爬起来重下文件的经历,这篇文章应该能让你少熬几次夜。
2. Linux 断点续传的原理与三款工具的选型:wget、curl 与 aria2 谁能扛住掉线
断点续传不是玄学,它完全依赖 HTTP 协议里的一个约定:Range。你告诉服务器“我只要文件从第 500 字节开始的部分”,服务器认账就返回 206 Partial Content,不认账就返回 200 把整个文件倒给你。搞清楚这一点,你就明白为什么同一个下载命令有时候能续传,有时候却从头再来。
2.1 先搞懂断点续传在服务端是怎么认账的:HTTP Range 与 206
当你执行 wget -c 或 curl -C - 时,客户端会带上一个形如Range: bytes=500-的请求头,表示“我已经有前面 500 字节,把剩下的给我”。服务端如果支持,就返回206 Partial Content,同时用 Content-Range 告诉你可以继续的位置;如果不支持,则忽略 Range 头,返回200 OK,客户端只能重新下载。
所以在做任何大文件下载之前,我习惯先用 curl -I 看一下链接的响应头。这一步非常快,能帮你避开很多后面要踩的坑:
# 查看下载链接是否支持断点续传的关键响应头 curl -I -L 'https://mirrors.example.com/linux.iso' | grep -iE 'accept-ranges|content-length|HTTP/'如果结果里有Accept-Ranges: bytes,说明服务端支持分段请求,断点续传和多线程下载都可以往下做。如果看到Accept-Ranges: none或者压根没有这个字段,那后面无论你给 wget 加多少-c,都只是在重新下载。注意-L要跟上,很多镜像站点会先给你一个跳转地址,最终落到对象存储或 CDN 节点,只有最后一个响应头才真正代表文件服务的行为。
这里也顺带说一个容易混淆的场景:git clone 断点续传。git clone 并不是按 HTTP Range 来续传单个大文件的,它走的是 git 协议或 SSH,传输对象是“一坨小对象”。中途断掉后,仓库目录里已经 fetch 下来的对象不会全部作废,但也没有一个简单的git clone -c可以拿来继续。我一般会用 shallow clone 降低单次传输量,或者干脆用 git fetch 分步拉取,而不是指望下载器续传。这个和普通文件下载是两套思路,不要混用。
2.2 wget、curl、aria2 三条命令的真实差异与选型
Linux 下最常见的下载工具就三个:wget、curl、aria2c。它们各自都能断点续传,但能力和适用场景差别很大。我把关键差异列成一张对照表:
| 工具 | 断点续传参数 | 多线程 | 适用场景 |
|---|---|---|---|
| wget | -c | 不支持(单线程) | 临时下载小文件、递归抓站 |
| curl | -C - | 不支持(单线程) | 接口调试、脚本里精确控制请求头 |
| aria2c | -c或--continue | 支持-x、-s | 大文件、批量下载、任务队列 |
三条命令对应的最小续传写法是这样的:
# 三种工具各自最小的续传命令 wget -c -O linux.iso 'https://mirrors.example.com/linux.iso' curl -C - -o linux.iso 'https://mirrors.example.com/linux.iso' aria2c -c -x 8 -s 8 -o linux.iso 'https://mirrors.example.com/linux.iso'wget 的-c最直观,但它的断点续传依赖服务端正确响应 Range。如果服务端返回 200,wget 会直接覆盖原文件,相当于前面白下了。curl 的-C -会自动从本地已存在文件的大小推断偏移量,行为比 wget 更严谨,但它仍然是单线程。aria2c 的-c配合-x 8 -s 8,既能断点续传,又能把文件拆成多段并行下载,是做 Linux 大文件下载的第一选择。
选型的结论很直接:临时拉一个小包有人给你链接,用 wget;在 shell 脚本里需要处理下载并查看请求头,用 curl;凡是文件超过几百 MB,或者你想彻底解决掉线重来问题,直接装 aria2。这三条都是 Linux 常用命令,但不要一杯水端到底,不同工具解决的问题不一样。
3. 多线程下载的落地:让 aria2c 按你想要的并发数跑起来
aria2c 的多线程原理不复杂:它把同一个文件按字节偏移拆成多个段,每段开一个连接去下载,最后拼回一个完整文件。你看到的是 8 个连接同时在跑,实际每个连接都在服务端的 Range 支持范围内工作。要做到这一点,关键是选对参数,别把它当成一把只会用最大连接数的锤子。
3.1 aria2c 的最小可用命令:多连接、分块与续传一起打开
我第一次用 aria2c 时以为只要-x 16就能拉满速度,结果发现连接是开了,但服务端根本不给这么多分段,进度也乱。后来才明白,-x控制每个服务器的最大连接数,-s控制文件拆分份数,--min-split-size决定多小的文件不值得拆。一个完整的最小命令长这样:
# 用 8 个连接分 8 段下载,每段至少 1M,支持断点续传 aria2c \ --dir=/data/download \ --out=linux.iso \ --max-connection-per-server=8 \ --split=8 \ --min-split-size=1M \ --continue=true \ 'https://mirrors.example.com/linux.iso'--dir指定下载目录,--out指定输出文件名。--max-connection-per-server=8表示对同一台服务器最多开 8 个连接;--split=8表示把文件分成 8 段。这里的逻辑是:如果文件只有 10MB,--min-split-size=1M会让它实际只拆成 10 段以内,不会为了凑 8 个连接硬拆出一个 300KB 的碎片段。--continue=true是打开断点续传的开关,中断后再次执行相同命令,aria2c 会从.aria2控制文件里读取进度继续。
注意一个细节:aria2c 下载时会生成一个带有.aria2后缀的同名控制文件,这个文件保存了每一段已完成的位置。下载成功后它会自动消失。如果任务中断,看到这个控制文件不要删,它是你的后悔药,删了就彻底失去续传依据。
3.2 并发数不是越大越好:线程数、分块大小与限速的平衡
很多人在云服务器上开-x 16,以为连接越多越好,结果被 CDN 直接限流,甚至触发安全策略,返回 403。原因很现实:服务器的带宽、CPU、磁盘 IO 都是资源,单条 TCP 连接也不一定能占满你的下行带宽。我自己的经验是,普通文件服务器用 4 到 8 个连接就足够,慢速大文件可以到 12,但超过 16 基本没有收益。
分块大小也会影响稳定性。--min-split-size设置太小,比如 64K,会让 aria2c 为几 GB 的文件生成几十上百个段,管理和合并的开销反而变大。常见的做法是 1M 到 5M。下面这个命令在并发之外加上了总速度限制,适合多人共用的服务器,避免下载占满出口带宽:
# 限制总下载速度最大 10M/s,并输出日志 aria2c \ --dir=/data/download \ -x 4 -s 8 -k 1M \ --max-download-limit=10M \ --continue=true \ --log=/var/log/aria2.log \ 'https://mirrors.example.com/linux.iso'这里-x 4是连接数,-s 8是分段数,分段数可以大于连接数,多余的分段会排队。--max-download-limit=10M控制的是整个任务的总速度,不是你想象中每个连接 10M。--log参数在排查问题时非常有用,网络抖动导致多次重连时,日志里会留下明确的痕迹。
并发参数没有固定公式,但有一个可参考的起点:文件小于 100MB,-x 2足够;1GB 左右,-x 4 -s 4;10GB 以上,-x 8 -s 8起步。下载中途进程被杀,重新执行同一条命令,只要.aria2控制文件还在,它就能续上,不需要手动指定偏移。
3.3 一个完整的下载示例:用 aria2c 拉取 Linux 镜像
实际下载 Linux 镜像时,镜像站通常提供了官方校验文件。我的习惯是让 aria2c 下载完镜像后,立刻做一次 md5sum 校验,避免拿到一个残缺的 ISO 还去安装系统。下面的命令组合了目录准备、多线程下载和校验:
# 建目录并下载 Ubuntu 服务器镜像 mkdir -p /data/download && cd /data/download aria2c -x 8 -s 8 -k 1M -c \ 'https://mirrors.example.com/ubuntu/22.04/ubuntu-22.04-live-server-amd64.iso' # 如果手动下载了官方 md5 文件,校验整体性 md5sum -c ubuntu-22.04-live-server-amd64.iso.md5第一段命令先创建下载目录并进入,这样 aria2c 默认把文件写到当前目录。-x 8 -s 8是为镜像这类大文件准备的参数,即使中途断网,重跑一次这条命令,进度还会从断点继续。第二段命令的-c是--check的意思,注意这里它不是指续传,而是让 md5sum 读取 md5 文件里的逐条校验记录,一一比对。
如果你下载的不是镜像,而是一个资源包,比如标题里那种 download.rar,下载完成后先不要急着解压,先执行一次unrar t或者直接file download.rar确认文件类型。很多“下载完打不开”的问题,其实是因为 CDN 返回了一个 404 页面,文件名后缀是.rar,但内容根本不是压缩包。
4. 把 download.rar 场景变成脚本:一个支持断点续传的多线程下载器
命令背得再熟,到了生产环境还是需要脚本。原因很简单:下载任务往往不是一次性的,失败要重试,日志要留存,退出码要判断。与其每次手敲一长串 aria2c 参数,不如把常见的续传、并发、重试逻辑封装成一个 shell 脚本,放到任何 Linux 服务器上都能用。
4.1 封装脚本之前需要想清楚的三个约束
写脚本之前,先想想你真正需要它做什么。第一是任务中断后的恢复方式:脚本如果中途被杀,再跑一次必须能从上次的进度继续,也就是说脚本本身不能主动清理.aria2控制文件。第二是日志的可读性:你不能只输出到终端,因为 SSH 断开后你再想看输出就没了,必须同时写入日志文件。第三是退出码的语义:aria2c 的退出码不全是 0 和 1,不同退出码代表不同故障,脚本要根据退出码决定是否重试。
还有一个容易被忽略的约束:不要自己写分段下载逻辑。网上很多方案用 wget 配合后台进程分别下载不同字节段,最后 cat 合并,这个思路可行但非常容易翻车——某个段失败后整个文件就废了,你还得自己维护偏移量。aria2c 已经把分段和管理复杂度封装好了,脚本里只需要调用它。
4.2 download.sh 脚本:支持续传、并发、重试与校验
下面这个脚本是我在服务器上常用的模板,参数很简单:第一位是 URL,第二位是输出文件名,第三位是并发数。默认日志写到/var/log/downloader,失败后重试 5 次,每次间隔 5 秒。你可以直接保存为download.sh使用。
#!/usr/bin/env bash # download.sh - 使用 aria2c 实现多线程断点续传下载 # 用法: ./download.sh <url> [output_name] [threads] set -uo pipefail URL="$1" OUT="${2:-$(basename "$URL")}" THREADS="${3:-8}" LOG_DIR="${LOG_DIR:-/var/log/downloader}" LOG_FILE="${LOG_DIR}/${OUT}.log" MAX_RETRY="${MAX_RETRY:-5}" command -v aria2c >/dev/null 2>&1 || { echo "缺少 aria2c,请先安装(apt install aria2 / yum install aria2)" exit 1 } mkdir -p "$LOG_DIR" # 若存在旧的 .aria2 控制文件,说明上次任务未完成,保留它用于续传 retry=0 while [ $retry -lt $MAX_RETRY ]; do echo "[$(date '+%F %T')] 开始下载 ${URL},输出 ${OUT},线程 ${THREADS}(第 $((retry+1)) 次尝试)" | tee -a "$LOG_FILE" aria2c \ --dir="$(dirname "${OUT}")" \ --out="$(basename "${OUT}")" \ --max-connection-per-server="$THREADS" \ --split="$THREADS" \ --min-split-size=1M \ --continue=true \ --log-level=notice \ --log="$LOG_FILE" \ "$URL" exit_code=$? if [ $exit_code -eq 0 ]; then echo "[$(date '+%F %T')] 下载完成: ${OUT}" | tee -a "$LOG_FILE" break else retry=$((retry + 1)) echo "[$(date '+%F %T')] 下载中断,退出码 ${exit_code},等待 5 秒后重试" | tee -a "$LOG_FILE" sleep 5 fi done if [ $retry -ge $MAX_RETRY ]; then echo "达到最大重试次数(${MAX_RETRY}),仍未完成。" echo "相同命令再执行一次即可续传,.aria2 控制文件仍已保留。" exit 1 fi # 如果同时准备了 md5 值,做完整性校验 if [ -n "${MD5SUM:-}" ]; then echo "${MD5SUM} ${OUT}" | md5sum -c - >/dev/null 2>&1 \ && echo "[$(date '+%F %T')] MD5 校验通过" \ || echo "[$(date '+%F %T')] MD5 校验失败,请勿使用该文件" fi脚本里最值得注意的细节是exit_code=$?必须紧跟 aria2c 命令之后捕获,否则 echo 命令会把$?覆盖掉,重试逻辑就会拿错退出码。第二是set -uo pipefail,-u能在未定义变量时立刻报错,pipefail保证管线中任一环节失败都能被脚本感知。第三是--continue=true必须保留,很多失败的脚本一开始就默认它会续传,实际上这个参数如果不显式打开,aria2c 遇到同名文件会重新下载。
4.3 为什么不用 wget 做多线程,脚本里最常见的误用
我在维护下载脚本时看到过一种误用:用wget -c跑一个循环,断线后重新执行 wget,看起来像续传,实际因为服务端不支持 Range,每次都从 0 开始。这种脚本的问题在于日志里全是“重新下载”,你根本察觉不到文件其实一直在重来。换个方式,先用 curl -I 验证 Accept-Ranges,再决定要不要走续传,能省下大半天时间。
另一个常见误用是把并发数写死。同一个脚本,在公司内网下载一个 500MB 的安装包,-x 8很合适;但放到一个带宽只有 2M 的跳板机上,8 个连接反而让路由器丢包率升高。脚本里把线程数作为第三位参数而不是硬编码,是我现在坚持的习惯。
脚本完成后,后台运行和看日志这两条 Linux 常用命令也值得一起记住:
# 用 nohup 让下载脚本在 SSH 断开后继续跑 nohup ./download.sh 'https://mirrors.example.com/big.bin' big.bin 8 > download.log 2>&1 & # 实时跟踪日志 tail -f download.lognohup不会让脚本变成守护进程,但它能避免终端关闭时向进程发送 SIGHUP,配合&就能在后台运行。tail -f是排查下载卡住时的第一选择,如果日志长时间不变化,再用ps -ef | grep aria2c看进程是否还活着。
5. 断点续传与多线程下载的常见坑:从“显示继续但重新下载”到“文件被改”
再好的脚本也会遇到外部环境的刁难,断点续传这块的坑基本都是服务端行为、控制文件状态、磁盘空间三者之一出了问题。下面五条是我踩过或者帮别人踩过的真实记录,每一条都按现象、原因、解决三步写清楚。
5.1 现象:明明加了 -c,进度却从 0 开始
这是最多的投诉。你执行wget -c,输出的第一行还写着“已经获取到 500M,还有 100M”,下一秒它就从 0 开始下载了。原因通常不是命令写错,而是服务端忽略了 Range 请求,直接返回 200。很多静态站、预览服务器并不会认真处理 Range 头,尤其是一些基于旧框架的文件下载接口。
解决方法是先确认响应头。我给自己定过一个规矩:凡是需要断点续传的文件,先跑一次curl -I -L <url>,看到 Accept-Ranges 才用-c。如果是自己维护的下载服务,检查反向代理有没有把 Range 头传递给后端,比如 Nginx 的proxy_set_header Range $http_range;这是最容易漏的那一行。
5.2 现象:下载完成后文件 md5 值与官方发布不一致
下载“成功”了,但解压时提示压缩包损坏,或者 md5sum 对不上。可能的原因有两个:一是 CDN 缓存了不完整对象,你拿到的是一个切片损坏后的副本;二是 aria2c 并行下载时,某个连接返回了错误页,但状态码恰好是 206,导致错误内容被写进了分段。
解决方式是在脚本里强制加入完整性校验。如果文件下载到本地名为download.rar,官方 md5 与文件同目录下,直接用md5sum -c download.rar.md5。这个习惯不用每次手动执行,写进 download.sh 的收尾部分即可。另外,下载完成后先用file download.rar看一眼文件类型,如果显示 HTML 或 text,基本就是 CDN 拦截或者 404 兜底页。
5.3 现象:aria2 中断后,再次运行不续传而是重新下
你重新执行了同一条 aria2c 命令,但它没有读.aria2控制文件,反而在目录里生成了一个新文件。最常见的原因是下载目录里已经存在一个同名文件,并且这个文件不是你上一次下载留下的,或者它的修改时间比控制文件还新。aria2c 出于安全考虑,不敢直接用控制文件去覆盖一个未知状态的文件。
解决方法是先查看目录里download.rar和download.rar.aria2的 mtime,如果控制文件有时间,而目标文件被临时脚本清理过,就要把.aria2控制文件和目标文件放在同一个目录下。还有一点,--auto-file-renaming=true是 aria2c 的默认值,它会在同名文件存在时自动改名成download(1).rar,让你误以为没有续传。续传任务建议显式加--auto-file-renaming=false,确保文件名不变。
5.4 现象:下载目录磁盘空间不足,任务卡住
多线程下载时空间消耗往往比你预期的多。每个分段都有自己的写入偏移,aria2c 看起来是最终文件大小的占用,但某些情况下临时文件会占用更多空间,比如分段尚未合并时,控制文件记录带来的额外开销。df -h显示还有 1GB 剩余,但一个 800MB 的文件下载到最后一步时却报No space left on device。
解决方式是为下载目录预留最终文件大小 10%~20% 的余量。大数据下载之前,用du -sh和df -h检查。另外不要用/tmp作为下载目录,很多发行版把/tmp挂载为 tmpfs,重启后数据全没,那你的.aria2控制文件也跟着消失,断点续传自然无从谈起。服务器下载大文件,我一般在/data/download这类持久化磁盘上做。
5.5 现象:文件大小正确,但分段边界处有损坏
这个坑比较隐蔽,文件总字节数和服务端 Content-Length 一致,但解压时偏偏在某个位置报错。原因是部分 CDN 节点对 Range 响应的边界处理不严谨,多线程下载时两个分段交接的位置多了一段或少了一段,而 aria2c 在合并时没有发现字节总数异常。
解决方式是把分段数降下来重新下载,或者直接用单线程命令测试那段区域。如果 CDN 对 Range 支持不完整,多线程确实不适合这个资源,这时宁可慢一点也要先保证文件正确。另外,如果资源本身是 rar 压缩包,下载后可以用rar t download.rar做一次测试解压,它能直接告诉你哪个分卷受损,这比 md5 更快定位问题。
6. 进阶:把断点续传下载放到任务队列里,同时盯住下载完整性
上面的脚本只解决“一个文件下载”的问题,但实际工作中往往有一批 URL 要拉,尤其是镜像同步、资源包分发这种重复任务。aria2c 自带的 session 机制比写一堆外部脚本更合适。
你可以把多个 URL 放在一个 session 文件里,aria2c 负责调度。它的两个参数--save-session和--input-file配合起来,能做到进程重启后任务列表自动恢复。命令如下:
# 第一次下载时保存 session,后续中断后可直接从 session 续传 aria2c --dir=/data/download -x 8 -s 8 -c \ --save-session=/data/aria2.session \ 'https://mirrors.example.com/a.iso' \ 'https://mirrors.example.com/download.rar' # 下次从 session 文件加载全部任务 aria2c --dir=/data/download -x 8 -s 8 -c \ --input-file=/data/aria2.sessionsession 文件里保存的不只是 URL,还有每条 URL 对应的控制文件路径、下载临时状态。就算服务器重启,只要 session 文件在,--input-file会把未完成任务带回来。我还会给--save-session-interval=60,让 aria2c 每分钟自动把进度写回 session 文件,断电丢状态的窗口就非常小了。
完整性校验和 session 要保持同一条收尾逻辑。算完 md5 不急着庆祝,先看 aria2c 的退出码和日志里的Download complete信息,然后对 session 里每个任务做file类型抽查。如果发现某个文件是 HTML 错误页,就要从 session 里剔除后重新拉。
现在我每台下载服务器都保留一份 download.sh 模板和固定的 session 路径,新任务到了就扔给这个模板跑。大文件下载不是拼谁的单线程快,而是拼掉线之后恢复成本有多低。如果你准备长期处理 Linux 镜像、数据集或资源包,这套断点续传加多线程的组合可以省去大量重复劳动。希望帮到你。
本文还有配套的精品资源,点击获取