吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 424|回复: 7
上一主题 下一主题
收起左侧

[学习记录] HTTP Range 分段下载:断点续传到底是怎么做到的?(附最小实现)

  [复制链接]
跳转到指定楼层
楼主
chendanwen 发表于 2026-11-2 07:22 回帖奖励
最近把一个 15G 的模型文件从境外源拉回本地,单连接跑了两小时还没完,换成 8 段并发 + Range 续传之后几分钟就好了。中间踩的坑记一下,代码思路在最后一节。

一、Range 请求是什么

请求头加:
  Range: bytes=0-1048575

服务端如果支持,会返回 206 Partial Content,并在响应头里给:
  Content-Range: bytes 0-1048575/15242807270

最后那个数字是文件总长,第一段拿到它,才有资格分段。

二、分段下载的完整流程

1. 先 GET 第一段,从 Content-Range 里取总长 total
2. 把 total 切成 N 段,每段算出 start/end
3. 每段发带 Range 的请求,写进同一个文件的对应偏移
4. 全部完成后:逐段长度校验 + 整文件 sha256 比对

写同一个文件的不同偏移,用 seek 之后 write(或 pwrite),
不要真下成 N 个碎片再合并——大文件合并要多吃一块磁盘。

三、三个会踩的坑

坑1:服务端不一定支持 Range
先看响应头有没有 Accept-Ranges: bytes。没有就只能单连接。

坑2:断了要能接着来
每段自己记 downloaded 偏移,重启时从 start+downloaded 继续发 Range。
没有这层记录,"断点续传"就是假的。

坑3:分片不能贪多
8 段在多数家用宽带上就到顶,16/32 段反而掉速(TCP 拥塞控制互相踩)。

四、校验

分段最容易出"总长度对、内容错位"。所以每段写完核对长度,
最后整文件算一次 sha256,和源站给的 hash 比对。

五、最小实现(Python 伪代码)

def download(url, path, parts=8):
    total = get_total(url)            # 第一段拿 Content-Range
    bounds = split(total, parts)
    with open(path, 'r+b' if exists else 'wb') as f:
        for (s, e) in bounds:
            req = Request(url, headers={'Range': f'bytes={s}-{e}'})
            with urlopen(req) as r:
                f.seek(s)
                f.write(r.read())
    assert os.path.getsize(path) == total

这段代码不需要第三方库,urllib 就够。要点只有两个:seek 写偏移、拿到真实总长。

(发帖的规矩我懂:不放任何外部网址,也不提工具名。纯技术贴。)

吾爱-客户端-1.png (88.93 KB, 下载次数: 0)

吾爱-客户端-1.png

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
苏紫方璇 发表于 2026-11-2 09:40
下次别用ai写了
3#
da12006 发表于 2026-11-2 09:56
4#
Jly 发表于 2026-11-2 11:29
5#
kulouxiaohai 发表于 2026-11-2 11:30
这个完整源码有吗?
6#
 楼主| chendanwen 发表于 2026-11-2 12:18 |楼主
行,是我写得太像稿子了,这个我认。

代码在这里,一个文件就能跑,只用标准库:
import os, sys, hashlib, urllib.request
from concurrent.futures import ThreadPoolExecutor

def head_total(url):
    req = urllib.request.Request(url, method='HEAD')
    with urllib.request.urlopen(req) as r:
        cr = r.headers.get('Content-Range')
        if cr and '/' in cr:
            return int(cr.split('/')[-1])
        n = r.headers.get('Content-Length')
        return int(n) if n else 0

def fetch(url, path, parts=8):
    total = head_total(url)
    if not total:
        raise SystemExit('拿不到文件总长,可能不支持 Range')
    seg = total // parts
    bounds = []
    for i in range(parts):
        s = i * seg
        e = (total - 1) if i == parts - 1 else (s + seg - 1)
        bounds.append((s, e))

    def one(b):
        s, e = b
        done = 0
        while done < (e - s + 1):
            start = s + done
            req = urllib.request.Request(url, headers={'Range': 'bytes=%d-%d' % (start, e)})
            with urllib.request.urlopen(req) as r, open(path, 'r+b') as f:
                f.seek(start)
                while True:
                    chunk = r.read(1 << 20)
                    if not chunk:
                        break
                    f.write(chunk)
                    done += len(chunk)
        return done

    if not os.path.exists(path):
        with open(path, 'wb') as f:
            f.truncate(total)
    with ThreadPoolExecutor(max_workers=parts) as ex:
        list(ex.map(one, bounds))

    size = os.path.getsize(path)
    h = hashlib.sha256()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(1 << 20), b''):
            h.update(chunk)
    print('size', size, 'of', total, 'sha256', h.hexdigest()[:16])

if __name__ == '__main__':
    fetch(sys.argv[1], sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)

几点实话:

- 这段只做分段、续传、校验。没写并发调度、重试退避、限速,也没处理服务端不支持 Range 的降级,那些是另一个量级的事。
- 段数别贪多,我这边 8 段最快,16/32 反而掉速(拥塞控制互相踩)。
- 断点续传这里靠的是"每段自己记 done 偏移",重启后再跑会从 0 开始覆盖,真要断电续传得把 done 落盘。

实测:15G 文件单连接 2 小时没下完,8 段并发 6 分多钟。
7#
 楼主| chendanwen 发表于 2026-11-2 12:20 |楼主
kulouxiaohai 发表于 2026-11-2 11:30
这个完整源码有吗?

源码在 6 楼,直接复制就能跑(只用标准库)。
里面没写并发调度和重试退避,需要的话我再补。
8#
formeeting 发表于 2026-11-2 17:04
如此说来,这是turbofetch的底层原理咯。穷人要怎么搞到一个turbofetch授权码
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-11-4 04:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表