C# 文件下载之断点续传实现代码
注意,本文所说的断点续传特指HTTP协议中的断点续传。本文主要聊聊思路和关键代码,更多细节请参考本文附带的demo。
工作原理
HTTP协议中定义了一些请求/响应头,通过组合使用这些头信息。我们可以在一次HTTP请求中只请求一个文件中的一部分数据。这样我们就可以把已经下载的数据存起来,下次只用请求剩余的数据即可,当全部数据都下载到本地后再完成合并工作。
HTTP协议指出,可以通过HTTP请求中的Range头指定请求数据的范围,Range头的使用也很简单,只要指定下面的格式就可以了:
Range:bytes=500-999
它的意思是,只请求目标文件的第500到第999这500个字节。
比如我有一个1000bytes大小的文件需要下载,第一次请求时不用指定Range头,表示下载整个文件。但在下载完第499个字节后,下载被取消了。那么在下一次请求下载同一个文件时,只需要下载第500个字节至第999个字节的数据就可以了。原理看上去很简单,但我们需要考虑下面几个问题:
1.是不是所有的web服务器都支持Range头?
2.多次请求之间可能会间隔很长的时间,服务器上的文件发生了变化怎么办?
3.如何保存下载的部分数据和相关信息?
4.当我们通过字节操作把一个文件拼成原始大小后,如何验证它和源文件一模一样?
下面我们就带着这些问题去探究断点续传的一些细节。
检查服务器端对断点续传的支持
在服务器响应我们的请求时,会在响应头中通过Accept-Ranges指明是否接受请求一个资源的一部分数据。但这里似乎有个小小的陷阱,就是不同的服务器可能返回不同的值来指明自己能够接受部分资源的请求。貌似比较统一的方法是,当服务器不支持请求部分数据时,都会返回Accept-Ranges:none,我们只要判断这个返回值是不是等于none就行了。代码如下:
privatestaticboolIsAcceptRanges(WebResponseres) { if(res.Headers["Accept-Ranges"]!=null) { strings=res.Headers["Accept-Ranges"]; if(s=="none") { returnfalse; } } returntrue; }
检查服务器端文件是否变化
当我们下载了一个文件的一部分之后,可能马上就会接着下载,也可能会过一段时间再下载,也可能永远不会再接着下载了…
这里的问题是,当下次要接着下载时,如何确定服务器上的文件还是当初下载了一半的那个文件。如果服务器上的文件已经更新了,那无论如何都需要重新从头开始下载。只有在服务器上的文件没有发生变化的情况下,断点续传才有意义。
对于这个问题,HTTP响应头为我们提供了不同的选择。ETag和Last-Modified都能完成任务。
先看ETag:
TheETagresponse-headerfieldprovidesthecurrentvalueoftheentitytagfortherequestedvariant.(引自RFC261614.19ETag)
简单点说ETag就是一个标识当前请求内容的字符串,当请求的资源发生变化后,对应的ETag也会变化。好了,最简单的办法是第一次请求时,把响应头中的ETag存下来,下次请求时做比较。代码如下:
stringnewEtag=GetEtag(response); //tempFileName指已经下载到本地的部分文件内容 //tempFileInfoName指保存了Etag内容的临时文件 if(File.Exists(tempFileName)&&File.Exists(tempFileInfoName)) { stringoldEtag=File.ReadAllText(tempFileInfoName); if(!string.IsNullOrEmpty(oldEtag)&&!string.IsNullOrEmpty(newEtag)&&newEtag==oldEtag) { //Etag没有变化,可以断点续传 resumeDowload=true; } } else { if(!string.IsNullOrEmpty(newEtag)) { File.WriteAllText(tempFileInfoName,newEtag); } } privatestaticstringGetEtag(WebResponseres) { if(res.Headers["ETag"]!=null) { returnres.Headers["ETag"]; } returnnull; }
再来看看Last-Modified:
TheLast-Modifiedentity-headerfieldindicatesthedateandtimeatwhichtheoriginserverbelievesthevariantwaslastmodified.(引自RFC261614.29Last-Modified)
Last-Modified就是所请求的资源在服务器上的最后一次修改时间。使用方法和ETag大体相同。
个人感觉使用ETag和Last-Modified中的任何一个都能达到我们的目的。但是你也可以两个都用,做doublecheck,谁知道web服务器的实现是不是严格遵循了HTTP协议!
保存中间结果
这里主要就是用C#进行文件操作。大体思路是如果有未下载完的文件,就把新下载的字节添加到文件的末尾,不再啰嗦,有兴趣的同学请直接看demo代码。
验证文件
在断点续传的过程中,我们以byte为单位下载、合并文件,如果整个过程中稍有没有处理好的异常,可能最后得到的文件就和源文件不太一样。因此最好是能够对下载好的文件进行一次校验。可这也是最难、最不容易实现的。因为它需要服务器端的支持,比如服务器端在提供一个可下载文件的同时提供该文件的MD5hash。当然,如果服务器端也是我们自己创建的,我们就可以去实现它。但我们又怎么能够要求现存的web服务器都提供这样的功能呢!
Demo下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。