C# 文件下载之断点续传
admin
2023-03-23 01:01:02
0

注意,本文所说的断点续传特指 HTTP 协议中的断点续传。本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo。

工作原理

HTTP 协议中定义了一些请求/响应头,通过组合使用这些头信息。我们可以在一次 HTTP 请求中只请求一个文件中的一部分数据。这样我们就可以把已经下载的数据存起来,下次只用请求剩余的数据即可,当全部数据都下载到本地后再完成合并工作。

HTTP 协议指出,可以通过 HTTP 请求中的 Range 头指定请求数据的范围,Range 头的使用也很简单,只要指定下面的格式就可以了:

Range: bytes=500-999

它的意思是,只请求目标文件的第 500 到第 999 这 500 个字节。

比如我有一个1000 bytes 大小的文件需要下载,第一次请求时不用指定 Range 头,表示下载整个文件。但在下载完第 499 个字节后,下载被取消了。那么在下一次请求下载同一个文件时,只需要下载第 500 个字节至第 999 个字节的数据就可以了。原理看上去很简单,但我们需要考虑下面几个问题:

1.    是不是所有的 web 服务器都支持 Range 头?
2.    多次请求之间可能会间隔很长的时间,服务器上的文件发生了变化怎么办?
3.    如何保存下载的部分数据和相关信息?
4.    当我们通过字节操作把一个文件拼成原始大小后,如何验证它和源文件一模一样?

下面我们就带着这些问题去探究断点续传的一些细节。

检查服务器端对断点续传的支持

在服务器响应我们的请求时,会在响应头中通过 Accept-Ranges 指明是否接受请求一个资源的一部分数据。但这里似乎有个小小的陷阱,就是不同的服务器可能返回不同的值来指明自己能够接受部分资源的请求。貌似比较统一的方法是,当服务器不支持请求部分数据时,都会返回 Accept-Ranges: none,我们只要判断这个返回值是不是等于 none 就行了。代码如下:

C# 文件下载之断点续传

private static bool IsAcceptRanges(WebResponse res)
{    if (res.Headers["Accept-Ranges"] != null)
    {        string s = res.Headers["Accept-Ranges"];        if (s == "none")
        {            return false;
        }
    }    return true;
}

C# 文件下载之断点续传

检查服务器端文件是否变化

当我们下载了一个文件的一部分之后,可能马上就会接着下载,也可能会过一段时间再下载,也可能永远不会再接着下载了…
这里的问题是,当下次要接着下载时,如何确定服务器上的文件还是当初下载了一半的那个文件。如果服务器上的文件已经更新了,那无论如何都需要重新从头开始下载。只有在服务器上的文件没有发生变化的情况下,断点续传才有意义。
对于这个问题,HTTP 响应头为我们提供了不同的选择。ETag 和 Last-Modified 都能完成任务。

先看 ETag:

The ETag response-header field provides the current value of the entity tag for the requested variant. (引自RFC2616 14.19 ETag)
简单点说 ETag 就是一个标识当前请求内容的字符串,当请求的资源发生变化后,对应的 ETag 也会变化。好了,最简单的办法是第一次请求时,把响应头中的 ETag 存下来,下次请求时做比较。代码如下:

C# 文件下载之断点续传

string newEtag = GetEtag(response);// tempFileName指已经下载到本地的部分文件内容// tempFileInfoName指保存了Etag内容的临时文件if (File.Exists(tempFileName) && File.Exists(tempFileInfoName))
{    string oldEtag = File.ReadAllText(tempFileInfoName);    if (!string.IsNullOrEmpty(oldEtag) && !string.IsNullOrEmpty(newEtag) && newEtag == oldEtag)
    {    // Etag没有变化,可以断点续传
        resumeDowload = true;
    }
}else{    if (!string.IsNullOrEmpty(newEtag))
    {
        File.WriteAllText(tempFileInfoName, newEtag);
    }
}private static string GetEtag(WebResponse res)
{    if (res.Headers["ETag"] != null)
    {        return res.Headers["ETag"];
    }    return null;
}

C# 文件下载之断点续传

再来看看 Last-Modified:

The Last-Modified entity-header field indicates the date and time at which the origin server believes the variant was last modified. (引自RFC2616 14.29 Last-Modified)
Last-Modified 就是所请求的资源在服务器上的最后一次修改时间。使用方法和 ETag 大体相同。

个人感觉使用 ETag 和 Last-Modified 中的任何一个都能达到我们的目的。但是你也可以两个都用,做 double check,谁知道web服务器的实现是不是严格遵循了 HTTP 协议!

保存中间结果

这里主要就是用 C# 进行文件操作。大体思路是如果有未下载完的文件,就把新下载的字节添加到文件的末尾,不再啰嗦,有兴趣的同学请直接看 demo 代码。

验证文件

在断点续传的过程中,我们以 byte 为单位下载、合并文件,如果整个过程中稍有没有处理好的异常,可能最后得到的文件就和源文件不太一样。因此最好是能够对下载好的文件进行一次校验。可这也是最难、最不容易实现的。因为它需要服务器端的支持,比如服务器端在提供一个可下载文件的同时提供该文件的 MD5 hash。当然,如果服务器端也是我们自己创建的,我们就可以去实现它。但我们又怎么能够要求现存的 web 服务器都提供这样的功能呢!


相关内容

热门资讯

司机因操作不当引发车祸致2死6... 极目新闻记者 谢茂5月10日13时40分许,重庆黔江区一处十字路口发生一起交通事故。据重庆市公安局黔...
中国公民涉嫌非法持有武器,驻泰... 问:泰国警方日前表示,一中国在泰公民涉嫌非法持有武器,泰警方已开展调查。请问中国驻泰国使馆对此有何评...
榴莲商家千里维权,“仅退款”不... 近日,一则“河南商家驱车1600公里赴山东寻找‘仅退款’买家”的消息,引发社会关注。买家被行政拘留5...
原创 魏... 懦夫一生数死,丈夫只死一遭。莎士比亚的这句名言犹如利刃,直刺人心。他用极为尖锐的语言提醒我们:真正的...
三星的撤退与进击 文 | 华商韬略 苏州工业园。 这片土地,承载着三星在中国制造业版图中最具象征意义的记忆。 三十多...
利好!AI、算力,大消息! 重点关注 近日,国家发展改革委主任郑栅洁赴上海人工智能实验室,专题调研人工智能发展情况。 国家网信办...
原创 这... 在繁忙的都市生活中,我们常常被压力和疲惫所困扰。然而,有一种植物,它以其古朴、典雅的姿态,静静地绽放...
两台Figure AI人形机器... IT之家 5 月 9 日消息,当地时间 5 月 8 日,Figure AI 公布了一段视频:两台人形...
首款,成功研制!我国“太极计划... 记者5月9日从中国科学院力学研究所获悉,我国空间引力波探测“太极计划”核心测量系统研制工作取得重要进...
DeepSeek狂融500亿,... 梁文锋终于想通了。 过去一直拒绝融资,但现在则放开手脚。 最新市场消息显示,DeepSeek计划融资...