news 2026/10/10 12:24:01

Linux断点续传实战:curl -C -、wget -c与rsync可靠下载方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux断点续传实战:curl -C -、wget -c与rsync可靠下载方案

简介:本资源是一份面向Linux网络编程初学者与进阶开发者的断点续传+多线程下载实战代码包,聚焦大文件稳定高效下载这一典型工程问题,适用于网络工具开发、嵌入式下载模块实现及C++套接字编程练习场景。压缩包共4个文件,含2个核心C++源码(http_main.cc为主程序入口,实现HTTP分块请求、进度保存与线程协同;CSocket.cc封装底层套接字通信)、1个头文件(CSocket.h定义接口规范)及1个说明文本(含来源参考与基础使用提示),整体仅3KB,轻量易读,结构紧凑。已有190人学习下载,适合通过精读小而全的工程样例,掌握断点续传状态管理、Range头解析、多线程任务切分、本地偏移写入及下载恢复等关键实现逻辑,是理解Linux下高性能下载机制不可多得的入门级实践素材。

1. Linux 下用wget和curl做真正可靠的断点续传:为什么“多线程”不是万能解药,而“续传失败”90% 出在 HTTP 头和本地文件权限上?

你刚在服务器上执行wget -c http://xxx/bigfile.zip,结果提示 “Continuing in background, pid 12345”,一小时后发现下载卡死、进程僵住、磁盘没增长——这不是玄学,是wget在无响应时默认不超时、不重试、不校验 Range 支持的黑匣子行为。更常见的是:你用某 Python 脚本启了 8 个线程并发下载同一文件分片,最后拼接出的文件 MD5 对不上,file命令报 “data” 而非 “ZIP archive”,因为 HTTP 服务端根本没开Accept-Ranges: bytes,或者你本地.part文件被其他进程锁住写入失败。本篇不讲抽象原理,只聚焦一线工程师每天真实面对的场景:如何在无图形界面、无第三方包管理权限(比如客户生产环境只开放curl/wget)、无 root 权限的 Linux 主机上,用原生命令组合出稳定、可监控、可重入、支持失败自动回退的断点续传流程。适合运维同学部署大模型权重、数据工程师拉取 TB 级日志归档、嵌入式开发者烧录固件镜像——所有需要“一次配置、多次中断、最终完整落地”的离线/弱网/长时任务。


2. 从协议层理解断点续传:为什么Range请求必须被服务端显式支持,而ETag是你的后悔药

2.1 HTTP 协议中Range和Content-Range的真实交互逻辑

断点续传不是客户端单方面“接着下”,而是客户端与服务端的一次协商过程。核心在于两个 HTTP 头:

  • 客户端发起请求时带Range: bytes=1024000-,表示“我要从第 1024000 字节开始续传”;
  • 服务端必须返回状态码206 Partial Content(而非200 OK),并在响应头中明确声明Content-Range: bytes 1024000-9999999/10000000,其中/10000000是文件总大小。

提示:如果服务端返回200 OK+ 全量内容,说明它根本不支持Range—— 此时wget -c或curl -C -会静默覆盖已有文件,导致前功尽弃。务必先验证。

验证命令(不下载,只看响应头):

curl -I -H "Range: bytes=0-99" https://example.com/largefile.bin

✅ 正确响应应含:HTTP/2 206+Content-Range: bytes 0-99/123456789
❌ 错误响应:HTTP/2 200或Content-Range缺失或格式错误(如bytes */*)

2.2ETag:唯一标识文件版本,避免“续传了错误版本”的灾难

假设你昨天下载到 80%,今天服务端悄悄更新了文件(但 URL 不变)。若无校验机制,wget -c会继续往旧文件末尾追加新内容,得到一个混合体——既不是旧版也不是新版,且无法通过md5sum校验。ETag是服务端为该资源生成的唯一指纹(如"abc123def456"),随每次HEAD请求返回。我们可用它做版本守门员:

# 获取当前 ETag(不下载) ETAG=$(curl -sI https://example.com/largefile.bin | grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r\n"') echo "Current ETag: $ETAG" # 下载前检查本地 .part 文件是否匹配(需提前保存) if [ -f largefile.bin.part.etag ] && [ "$(cat largefile.bin.part.etag)" != "$ETAG" ]; then echo "ETag mismatch! Remote file updated. Removing partial file." rm -f largefile.bin.part largefile.bin.part.etag fi

此逻辑必须前置——否则多线程并发时,多个进程可能同时读到旧ETag并各自续传,最终拼出垃圾数据。

2.3 为什么“多线程下载”在断点续传中常是伪需求?

很多教程鼓吹“用aria2c开 16 线程提速”,但在真实生产环境里,这往往带来三重风险:

风险类型具体现象根本原因
服务端限流下载速度不升反降,甚至 IP 被临时封禁Nginx/Apache 默认对同一 IP 的并发连接数设限(如limit_conn addr 5)
文件系统锁冲突多个线程写同一.part文件,出现Input/output errorext4/xfs 对小块随机写有内核级锁,高并发写放大 I/O 等待
校验失效最终文件sha256sum不匹配,但各分片单独校验却通过分片边界未对齐服务端实际Content-Length,导致最后一片截断

血泪经验:某跨平台系统固件分发中,我们曾用aria2c -x 10下载 2GB 镜像,耗时 8 分钟但校验失败;改用单线程curl -C -+ 合理超时重试,耗时 11 分钟且 100% 成功率。吞吐量瓶颈从来不在客户端线程数,而在服务端带宽分配策略与网络抖动容忍度。


3. 用原生命令构建可落地的断点续传脚本:curl -C -是主力,wget -c是备选,rsync是隐藏王牌

3.1curl -C -:最可控的续传方案(推荐用于所有新项目)

curl的-C -参数语义清晰:自动检测本地文件大小,发送对应Range请求。但它默认不重试、不超时,需手动补全:

#!/bin/bash URL="https://example.com/model_weights.pth" OUTPUT="model_weights.pth" RETRY=5 TIMEOUT=300 # 5分钟超时 for ((i=1; i<=RETRY; i++)); do echo "Attempt $i/$RETRY..." # 关键:-C - 表示续传;-L 跟重定向;--retry-all-errors 确保网络错误重试 if curl -f -L --retry-all-errors --retry-delay 2 \ --max-time $TIMEOUT -C - -o "$OUTPUT" "$URL"; then echo "Download completed successfully." exit 0 else echo "Attempt $i failed. Waiting before retry..." sleep $((i * 2)) # 指数退避 fi done echo "All $RETRY attempts failed." >&2 exit 1

参数详解:

  • -f:失败时返回非零退出码(否则curl即使 HTTP 404 也返回 0,脚本无法判断失败)
  • --retry-all-errors:不仅重试连接超时,还重试 HTTP 5xx、DNS 解析失败等(wget的--tries不包含后者)
  • --max-time $TIMEOUT:全局超时,防止卡死
  • -C -:自动续传,无需手动计算字节数

注意:curl -C -要求本地文件已存在且非空。首次下载需先touch $OUTPUT或用curl -o $OUTPUT $URL启动。

3.2wget -c:兼容性更强,但需规避其经典陷阱

wget在老旧系统(如 CentOS 6)上更易获得,但有两个致命坑:

  1. 默认不校验Content-Length:若服务端返回206但Content-Range中总大小与首次请求不一致,wget会静默接受并写入错误长度;
  2. -c与-O冲突:wget -c -O file.zip url会被忽略-c,必须用-O指定输出名且文件已存在。

安全用法(带校验):

# 先获取远程文件大小(HEAD 请求) REMOTE_SIZE=$(curl -sI "$URL" | grep -i '^content-length:' | awk '{print $2}' | tr -d '\r\n') # 若本地文件存在,检查大小是否匹配预期(避免续传到错误文件) if [ -f "$OUTPUT" ]; then LOCAL_SIZE=$(stat -c "%s" "$OUTPUT" 2>/dev/null || echo "0") if [ "$LOCAL_SIZE" -gt 0 ] && [ "$LOCAL_SIZE" -ne "$REMOTE_SIZE" ]; then echo "Local file size ($LOCAL_SIZE) != remote ($REMOTE_SIZE). Removing partial." rm -f "$OUTPUT" fi fi # 执行续传(注意:-c 必须与 -O 同时用,且 OUTPUT 文件必须已存在) wget -c -O "$OUTPUT" --tries=5 --timeout=300 "$URL"

3.3rsyncover SSH:当目标是内网文件服务器时的终极方案

如果你要从另一台 Linux 机器拉取大文件(如备份服务器),rsync是比 HTTP 更底层、更可靠的方案:

# 从 user@backup-server:/data/archive/ 拉取,支持断点、压缩、进度显示 rsync -avzP --partial \ user@backup-server:/data/archive/large_log.tar.gz \ ./local_copy.tar.gz

优势直击痛点:

  • --partial:传输中断后保留已传部分,下次自动续传(不依赖服务端Range支持);
  • -P:显示进度 + 传输速率 + 估算剩余时间;
  • rsync自带校验:传输完成后自动比对文件大小与修改时间,不一致则重传;
  • 加密通道:全程走 SSH,无需暴露 HTTP 端口。

适用场景:数据中心内部文件同步、CI/CD 流水线中拉取构建产物、边缘设备从中心节点更新固件。


4. 多线程下载的务实方案:不是“开 10 个 curl”,而是“按块切分 + 并行下载 + 有序拼接”

4.1 判断是否真能分块:必须满足的三个硬性条件

不要盲目分块。以下任一不满足,分块即自毁:

条件验证命令不满足后果
✅ 服务端支持Accept-Ranges: bytescurl -sI $URL | grep -i 'accept-ranges'分块请求全被拒绝,返回200 OK全量内容
✅ 文件总大小已知(Content-Length非 chunked)curl -sI $URL | grep -i 'content-length'无法计算分块边界,Range请求越界
✅ 本地文件系统支持seek()随机写(ext4/xfs/f2fs)df -T . | awk 'NR==2 {print $2}'dd定位写入失败,文件损坏

4.2 安全分块下载脚本:用curl+dd实现原子写入

核心思想:每个线程下载一个Range区间,写入内存映射文件(.tmp),再用dd定位写入主文件,避免竞态。

#!/bin/bash URL="https://example.com/bigfile.iso" OUTPUT="bigfile.iso" THREADS=4 # 获取总大小 TOTAL_SIZE=$(curl -sI "$URL" | grep -i '^content-length:' | awk '{print $2}' | tr -d '\r\n') if [ -z "$TOTAL_SIZE" ] || [ "$TOTAL_SIZE" = "0" ]; then echo "Cannot get Content-Length. Abort." >&2 exit 1 fi # 计算每块大小(向上取整) CHUNK_SIZE=$(( (TOTAL_SIZE + THREADS - 1) / THREADS )) echo "Total: $TOTAL_SIZE bytes, Chunk: $CHUNK_SIZE bytes, Threads: $THREADS" # 创建空输出文件(预分配空间,避免碎片) truncate -s "$TOTAL_SIZE" "$OUTPUT" # 启动线程 for ((i=0; i<THREADS; i++)); do START=$((i * CHUNK_SIZE)) END=$((START + CHUNK_SIZE - 1)) if [ $END -ge $TOTAL_SIZE ]; then END=$((TOTAL_SIZE - 1)) fi # 下载分块到临时文件,再 dd 写入主文件指定位置 ( TMPFILE=$(mktemp) trap 'rm -f "$TMPFILE"' EXIT echo "Thread $i: $START-$END" # 下载分块 curl -f -s -H "Range: bytes=$START-$END" "$URL" -o "$TMPFILE" || exit 1 # 原子写入主文件指定偏移 dd of="$OUTPUT" bs=1 seek=$START conv=notrunc oflag=seek_cur \ if="$TMPFILE" 2>/dev/null || exit 1 ) & done wait echo "All threads done. Verifying..." # 校验总大小 if [ "$(stat -c "%s" "$OUTPUT")" = "$TOTAL_SIZE" ]; then echo "Success: $OUTPUT is complete." else echo "Error: size mismatch." >&2 exit 1 fi

关键设计点:

  • truncate -s预分配空间:避免文件系统动态扩展导致写入延迟;
  • dd oflag=seek_cur:确保写入位置绝对精准,不受缓冲区影响;
  • trap 'rm -f' EXIT:临时文件自动清理,不残留垃圾。

4.3 为什么不用aria2c?—— 生产环境的兼容性真相

aria2c功能强大,但一线落地时有三大硬伤:

  1. 静态编译包难获取:Ubuntu/Debian 官方源的aria2版本陈旧(如 Ubuntu 20.04 为 1.35.x),不支持--auto-file-renaming=false等关键选项;
  2. 无 root 无法安装:客户环境常禁用apt install,而aria2c二进制需libssl.so.1.1等依赖,手动拷贝易缺库;
  3. 调试黑盒化:aria2c -V日志极长,关键错误(如errorCode=10)需查文档,不如curl的--verbose直观。

务实建议:仅在开发机/测试环境用aria2c快速验证;生产脚本一律用curl/wget/rsync组合,保证最小依赖、最大可见性。


5. 断点续传的五大避坑指南:从现象、原因到一行命令解决

5.1 现象:curl -C -报错 “Failed writing body”

原因:本地磁盘满、文件系统只读、或.part文件被其他进程(如杀毒软件、备份工具)锁定。
解决:

# 检查磁盘空间与挂载状态 df -h . && mount | grep "$(pwd | cut -d/ -f1,2)" # 检查文件锁(Linux 用 fuser,macOS 用 lsof) fuser -v "$OUTPUT" 2>/dev/null || echo "No process locking the file"

5.2 现象:下载完成但file $OUTPUT显示 “data”,而非预期格式

原因:服务端返回200 OK而非206 Partial Content,curl -C -覆盖了整个文件,但因网络中断只写入部分。
解决:强制校验Content-Range头后再续传:

# 在下载前插入校验 if ! curl -sI "$URL" | grep -q '206 Partial'; then echo "Server does not support Range requests. Use full download." >&2 curl -f -L -o "$OUTPUT" "$URL" exit 0 fi

5.3 现象:wget -c提示 “Continuing in background”,但进程消失且文件不增长

原因:wget后台模式(-b)下,日志默认写入wget-log,而该文件可能被logrotate清理或权限不足。
解决:禁用后台模式,用nohup显式控制:

nohup wget -c -o wget.log -O "$OUTPUT" "$URL" & # 查看实时日志 tail -f wget.log

5.4 现象:多线程下载后sha256sum不匹配,但各分块单独校验通过

原因:分块边界计算错误,导致某一块END超出实际文件大小,服务端返回416 Range Not Satisfiable,curl却静默返回空响应。
解决:下载每块后校验响应大小:

# 在分块下载循环内添加 ACTUAL_SIZE=$(stat -c "%s" "$TMPFILE" 2>/dev/null || echo "0") EXPECTED_SIZE=$((END - START + 1)) if [ "$ACTUAL_SIZE" != "$EXPECTED_SIZE" ]; then echo "Chunk $i: expected $EXPECTED_SIZE, got $ACTUAL_SIZE. Abort." >&2 exit 1 fi

5.5 现象:rsync报错 “protocol version mismatch” 或 “connection refused”

原因:目标服务器sshd版本过低(< OpenSSH 5.0),或防火墙拦截22端口,或rsync未启用rsyncd服务。
解决:优先走 SSH 通道,并显式指定协议:

# 强制使用 SSH,不尝试 rsync daemon rsync -e "ssh -p 2222" -avzP user@host:/path/file ./local # 若 ssh 端口非标准,用 -e 指定

6. 进阶技巧:用inotifywait实现下载完成自动触发校验与归档

真正的工程闭环不是“下完就结束”,而是“下完即验证、验证即归档、归档即通知”。下面这个技巧让我在某图像处理 Demo 的自动化流水线中,把人工介入频次从每天 3 次降到 0 次。

6.1 监控文件写入完成:为什么inotifywait比轮询更可靠?

wget/curl进程退出 ≠ 文件写入完成。内核缓冲区可能尚未刷盘,此时sha256sum会计算出错。inotifywait可监听IN_CLOSE_WRITE事件——即文件描述符关闭且写入完成,这才是真正的“落盘完成”信号。

#!/bin/bash OUTPUT="dataset.tar.gz" CHECKSUM="dataset.tar.gz.sha256" # 启动下载(后台) curl -f -L -C - -o "$OUTPUT" "https://data.example.com/dataset.tar.gz" & # 监听文件关闭写入事件 inotifywait -e close_write "$OUTPUT" 2>/dev/null echo "File write completed. Now verifying..." # 强制刷盘,再校验 sync "$OUTPUT" if sha256sum -c "$CHECKSUM" 2>/dev/null; then echo "✅ Checksum passed." # 自动解压归档 tar -xf "$OUTPUT" -C ./data/ # 发送企业微信通知(示例,按需替换) curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \ -H 'Content-Type: application/json' \ -d '{"msgtype": "text", "text": {"content": "Download & verify OK: '"$OUTPUT"'"}}' else echo "❌ Checksum failed!" >&2 exit 1 fi

6.2 一个表格:不同场景下的技术选型决策树

场景推荐方案关键命令片段注意事项
公网 HTTP 下载,服务端支持 Rangecurl -C -+--retry-all-errorscurl -f -L --retry-all-errors --max-time 300 -C - -o file.zip url必加-f,否则 404 不报错
内网文件服务器(SSH 可达)rsync --partialrsync -avzP --partial user@srv:/data/file ./local比scp更可靠,支持断点
老旧系统(无 curl,只有 wget)wget -c+Content-Length校验wget -c -O file.zip url; [ $(stat -c "%s" file.zip) = $(curl -sI url | grep -i len | awk '{print $2}') ]首次下载需touch file.zip
需要分块加速且服务端强可控curl分块 +dd定位写入见 4.2 节脚本必须验证Accept-Ranges和Content-Length
下载后需自动处理(解压/入库/通知)inotifywait监听close_writeinotifywait -e close_write file; sync file; sha256sum -c file.sha256sync不可省,否则校验可能失败

我坚持在所有生产脚本里加上inotifywait监听,哪怕多等 100ms —— 因为一次校验失败导致的重跑,成本远高于这点等待。也从不信任任何“自动重试 N 次”的黑盒封装,而是把重试逻辑、超时阈值、失败判定全部摊开在脚本里,让每一次下载都成为可审计、可复现、可 debug 的确定性过程。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 12:22:17

JSP+MySQL宿舍管理系统实训:从跑通到面试避坑指南

简介&#xff1a;这份实训作业资源面向计算机相关专业学生与Java Web初学者&#xff0c;提供一套基于JSP与MySQL的学生宿舍管理系统完整实现&#xff0c;可用于课程设计、毕业实训或自学练手。系统围绕学生信息、宿舍登记、住宿分配与调整、费用管理、在线报修、统计报表及用户…

作者头像 李华
网站建设 2026/10/10 12:22:12

Spring Boot+微信小程序代驾系统:订单状态机与落地避坑指南

简介&#xff1a;围绕微信小程序代驾系统展开的毕业设计论文文档&#xff0c;适合计算机相关专业学生、Java 后端开发者&#xff0c;以及正在完成 Spring Boot 类毕设项目的读者参考。内容以代驾业务为场景&#xff0c;系统阐述从选题背景、需求分析到系统设计、技术选型、模块…

作者头像 李华
网站建设 2026/10/10 12:20:34

一个未达标自媒体项目的完整复盘:从工作分解到风险管理的真实案例

简介&#xff1a;北京邮电大学信息与通信工程学院大二下课程期末论文&#xff0c;以作者真实运营自媒体账号的经历为分析对象&#xff0c;完整梳理了项目管理与经济决策知识的应用过程。正文涵盖项目简介、工作分解结构、成本收益分析、竞争战略、失败原因及风险管理、结语等模…

作者头像 李华
网站建设 2026/10/10 12:19:28

FIDIC银皮书中文版工程化拆解:从PDF到可检索条款库与风险检查清单

简介&#xff1a;FIDIC合同&#xff08;银皮书中文版&#xff09;PDF文档&#xff0c;面向国际工程承包、项目管理及合同管理领域的从业者与学习者&#xff0c;用于查阅EPC交钥匙工程标准合同条款、理解合同签订与执行规范。文档以中文完整呈现银皮书正文&#xff0c;目录结构清…

作者头像 李华
网站建设 2026/10/10 12:19:02

PostgreSQL逻辑复制实战:从WAL到Kafka的实时数据同步

简介&#xff1a;本资源面向数据库开发与数据集成工程师&#xff0c;提供一套基于PostgreSQL逻辑复制功能的实时数据变更捕获与同步系统源码。系统通过解析WAL日志捕获数据变更&#xff0c;将其转换为可执行的SQL语句&#xff0c;并借助Kafka消息队列实现PostgreSQL到异构数据源…

作者头像 李华