Skip to content

下载引擎总览 ​

下载子系统分四层:编排层决定「下什么、怎么分摊」,源注册表决定「URL 改写成谁」,分片层负责「一个文件怎么切、怎么并行」,并发治理层负责「这个域名现在允许多少条连接」。这页讲它们怎么串起来,以及批量下载时省流量的三级复用。

DownloadService 是唯一入口。批量下载走 DownloadBatchAsync:过滤已完成文件、本地复用、P2P 复用,然后把剩下的文件丢进 SemaphoreSlim 限流的并发流里。

每个文件进 DownloadFromAsync 后先做一次「能力判定」,决定走哪条物理路径:

csharp
if (size >= ChunkedFileDownloader.Threshold && range == true)
    await _chunked.DownloadAsync(...);   // 分片竞速
// 否则
await DownloadWholeFileAsync(...);      // 整文件 + 换源重试

range 来自 DownloadSourceRegistry.RangeSupport(host)——主机级记忆,没有记录就探一次。体积不够就直接整文件,不浪费探测;体积够但源不支持 Range 也只能整文件。

分片失败不会让整个下载失败,而是降级到整文件路径。降级是必要的兜底:分片路径有完整性闸门和整文件 SHA1 校验,任何一项不过都抛错,让整文件路径接手。

批量下载的三级复用 ​

第一级:目标已存在且大小相同,直接跳过:

csharp
if (File.Exists(f.Dest) && f.Size > 0 && new FileInfo(f.Dest).Length == f.Size)
    continue;

这里只比大小,不比哈希——多数文件本来就带官方 SHA1。

第二级:本地复用。LocalFileReuse.Scan 扫当前 .minecraft 的 versions/、各实例的 mods 目录,以及启动器自己的 library/ 目录。命中就 File.Copy 过去,不走网络。同版本 jar 是十几 MB 量级,这一项最省时间。

第三级:P2P。局域网内同账号的另一台设备已经有同 SHA1 文件时,直接从网内拉。只认 SHA1——没有哈希的条目一律不参与。

三级都是加速手段,任何一级出错都只是退回正常下载。

进度会重复上报

换源重试和断点续传都会把同一段字节再报一遍。直接累加,进度条会冲过 100%:

csharp
var raw = Interlocked.Read(ref doneBytes) + lastReported;
var doneNow = totalBytes > 0 ? Math.Min(raw, totalBytes) : raw;

totalBytes 是「未复用部分」的总大小,被复用的文件不参与这个分母——这也是为什么 pending.Count == 0 的分支要小心(会显示成 7300/3650)。

元数据请求必须有独立超时 ​

下载大文件的 HttpClient 是 Timeout.InfiniteTimeSpan,这是必须的——总时长超时会误杀慢速大文件(速度 × 60s ≈ 24MB 处必然被掐死)。但元数据请求走的是同一个 client,不加限制就会永远转圈。

原因很具体:BMCLAPI 会把请求 302 给 P2P 节点,实测约六成节点「TCP 连上了但永不应答」。用户看到的是「点了下载一直转」,无字节、无进度、无报错。

所以元数据走连接级 CTS,每个源 12 秒:

csharp
internal static TimeSpan MetadataTimeout = TimeSpan.FromSeconds(12);

源之间逐个尝试。池里只有一个源时给两次机会,Mojang 镜像池有多源时一次即换。

镜像返回 HTML 要当故障处理 ​

有些镜像的路径根本不存在,返回的是站点首页 HTML 而不是 404。GetStringAsync 拿到这种内容直接交给 JSON 解析的话,报错会是「意外的字符 '<'」,跟真实原因毫无关系。

我们用「首个非空白字符是不是 <」判断——合法 JSON 永远不会以 < 开头,这个判据足够:

csharp
private static bool LooksLikeHtml(string text)
{
    foreach (var c in text)
    {
        if (char.IsWhiteSpace(c)) continue;
        return c == '<';
    }
    return false;
}

命中就当这个源故障,换下一个。

相关链接:分片与竞速、镜像与源健康度、校验与断点续传

基于 MIT 许可证(有修改)开源