注意,本文所说的断点续传特指 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 就行了。代码如下:

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;
}

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

当我们下载了一个文件的一部分之后,可能马上就会接着下载,也可能会过一段时间再下载,也可能永远不会再接着下载了…
这里的问题是,当下次要接着下载时,如何确定服务器上的文件还是当初下载了一半的那个文件。如果服务器上的文件已经更新了,那无论如何都需要重新从头开始下载。只有在服务器上的文件没有发生变化的情况下,断点续传才有意义。
对于这个问题,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 存下来,下次请求时做比较。代码如下:

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;
}

再来看看 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 服务器都提供这样的功能呢!

Demo 下载

C# 文件下载之断点续传的更多相关文章

  1. 【SFTP】使用Jsch实现Sftp文件下载-支持断点续传和进程监控

    参考上篇文章: <[SFTP]使用Jsch实现Sftp文件下载-支持断点续传和进程监控>:http://www.cnblogs.com/ssslinppp/p/6248763.html  ...

  2. iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>

    前言:根据前篇<iOS开发之网络编程--2.NSURLSessionDownloadTask文件下载>或者<iOS开发之网络编程--3.NSURLSessionDataTask实现文 ...

  3. iOS开发之网络编程--3、NSURLSessionDataTask实现文件下载(离线断点续传下载)

    前言:使用NSURLSessionDownloadTask满足不这个需要离线断点续传的下载需求,所以这里就需要使用NSURLSessionDataTask的代理方法来处理下载大文件,并且实现离线断点续 ...

  4. iOS 文件下载及断点续传

    ios的下载我们可以使用的方法有:NSData.NSURLConnection.NSURLSession还有第三方框架AFNetworking和ASI 利用NSData方法和NSURLConnecti ...

  5. 【转】iOS 文件下载及断点续传

    ios的下载我们可以使用的方法有:NSData.NSURLConnection.NSURLSession还有第三方框架AFNetworking和ASI 利用NSData方法和NSURLConnecti ...

  6. Winform文件下载之断点续传

    在本系列的前两篇文章中,分别向大家介绍了用于完成下载任务的 WebClinet 和 WinINet 的基本用法和一些实用技巧. 今天来为大家讲述下载过程中最常遇到的断点续传问题. 首先明确一点,本文所 ...

  7. php大文件下载支持断点续传

    <?php   /** php下载类,支持断点续传  *  *   Func:  *   download: 下载文件  *   setSpeed: 设置下载速度  *   getRange: ...

  8. PHP超大文件下载,断点续传下载

    源代码: <?php $sourceFile = "1.tmp"; //要下载的临时文件名 $outFile = "用户订单.xls"; //下载保存到客 ...

  9. Java单线程文件下载,支持断点续传功能

    前言: 程序下载文件时,有时会因为各种各样的原因下载中断,对于小文件来说影响不大,可以快速重新下载,但是下载大文件时,就会耗费很长时间,所以断点续传功能对于大文件很有必要. 文件下载的断点续传: 1. ...

随机推荐

  1. logstash file输入,无输出原因与解决办法

    1.现象 很多同学在用logstash input 为file的时候,经常会出现如下问题:配置文件无误,logstash有时一直停留在等待输入的界面 2.解释 logstash作为日志分析的管道,在实 ...

  2. Java 输出流中的flush方法

    转自:http://blog.csdn.net/jiyangsb/article/details/50984440 java中的IO流中的输出流一般都有flush这个操作,这个操作的作用是强制将缓存中 ...

  3. 【Machine Learning】决策树案例:基于python的商品购买能力预测系统

    决策树在商品购买能力预测案例中的算法实现 作者:白宁超 2016年12月24日22:05:42 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本 ...

  4. 深入解析Sqlite的完美替代者,android数据库新王者——Realm

    写在前面: 又到一年一度七夕虐狗节,看着大家忍受着各种朋友圈和QQ空间还有现实生活中的轮番轰炸,我实在不忍心再在这里给大家补刀,所以我觉得今天不虐狗,继续给大家分享有用的. 如果你比较关心androi ...

  5. KOTLIN开发语言文档(官方文档) -- 2.基本概念

    网页链接:https://kotlinlang.org/docs/reference/basic-types.html 2.   基本概念 2.1.  基本类型 从可以在任何变量处理调用成员函数和属性 ...

  6. MongoDB学习笔记六—查询下

    查询内嵌文档 数据准备 > db.blog.find().pretty() { "_id" : ObjectId("585694e4c5b0525a48a441b5 ...

  7. SQL Server存储过程

    创建于2016-12-24 16:12:19 存储过程 概念: 1.存储过程是在数据库管理系统中保存的.预先编译的.能实现某种功能的SQL程序,它是数据库应用中运用比较广泛的 一种数据对象. 2.存储 ...

  8. JavaMail发送邮件

    发送邮件包含的内容有: from字段  --用于指明发件人 to字段      --用于指明收件人 subject字段  --用于说明邮件主题 cc字段     -- 抄送,将邮件发送给收件人的同时抄 ...

  9. IOS开发之—— 在AFN基础上进行的网络请求的封装

    网络请求的思路:如果请求成功的话AFN的responseObject就是解析好的. 1发送网络请求:get/post/或者别的 带上URL,需要传的参数 2判断后台网络状态码有没有请求成功: 3 请求 ...

  10. [PHP源码阅读]empty和isset函数

    近日被问到PHP中empty和isset函数时怎么判断变量的,刚开始我是一脸懵逼的,因为我自己也只是一知半解,为了弄懂其真正的原理,赶紧翻开源码研究研究.经过分析可发现两个函数调用的都是同一个函数,因 ...