一种简单的实现多线程断点续传的方案

(PS:前面四段是废话,对技术感兴趣的直接看图片后的部分)

由于VisualStudio 2010 RC版发布了,我自然在第一时间踊跃当起了小白鼠,便用迅雷下了起来。谁知道还没有下一个小时,就被一起租房的哥们抱怨了,说他现在连在网上看小说都不行了,问我还需要下载多久。由于我用的是网通的512K小水管,算了一下,大概要下一整天,便把迅雷给停了(迅雷的那个所谓智能限速完全没有效果)。

然而,看到那发布的VisualStudio 2010却有不能下载,确实又有点心痒。便想用IE直接下载(其实只要不像迅雷那样同时开数十甚至上百个TCP连接,如果只有几个线程,哪怕下载速度到了ADSL上限,也是是不会影响其它的网络业务的),但网通的网络环境又再一次叫我失望了(确实是网通的问题,微软还是很稳定的),下个几十分钟就不定时来一个失连,没速度了,得从头重新下载,看得让人只想哭。另外,单线程下载速度也不稳,速度在20k~50k之间飘忽飘忽的,不能维持在稳定的高速状态,得同时使用两~三个线程才能达到最大速度。

由于微软的下载源还是比较稳定的,只有找一个支持多线程及断点续传的下载工具才能达到理想速度,同时又不影响上网。但是目前网上这种比较纯粹的下载工具确实太少了,主流的迅雷快车之类的基本上都是海量连接,一用起来速度是上去了,但就没法上网了。记得以前微软有一个这种简单的多线程下载工具的,现在要用的时候却找不到了。

没办法,只有自己写了,便把以前写的FlvDownloader改造了一下,增加了断点续传的功能(由于flv视频网站基本上都不支持标准的断点续传,并且flv视频也比较小,片源速度也很稳定,便懒得弄这个功能)。由于以前就支持多任务并发控制功能,此次主要只是加了任务分割和断点续传功能,通过它成功的实现了VisualStudio的下载。

image 

多线程断点续传是一个很老的技术了,断点续传是关键(由于http流不支持随机访问,不支持断点续传也就谈不上多线程了),其原理也很简单,就是在http头中携带Range信息,从而使得可以从文件的一部分开始下载。对这部分不清楚的朋友可以参看一下这篇文章:
http://blog.csdn.net/fhbcn/archive/2009/01/15/3789035.aspx

在有了断点续传的基础后,下一步就是多线程了,整体流程如下:

  1. 首先探测文件的总体长度,根据长度成多个子任务,每个任务只下载文件的一部分。
  2. 子任务并发执行,所有任务执行完成后汇总为原始文件。

下面是FlashGet及迅雷等工具的实现方式:

  1. 首先创建文件,并根据文件长度分配好文件所需的空间(里面的内容都是无用的数据)。
  2. 根据文件长度和下载线程数分配每个任务的起始位置(Offset)和任务所需下载长度(Length)度,每个任务下载到了一定的数据后写入文件对应的位置。
  3. 当下载到一部分停止下载时,将子任务的进度记到一个配置文件中,下次续传时根据各子任务已经下载的进度重新分配任务。

这是一个非常成熟的方案,目前大多数多线程下载工具都是采用的这种方案。但此次我并没有采用这个方案,主要基于如下几个原因:

  1. 我以前的下载任务是基于单文件下载的,每个下载任务对应着一个完整的文件,如果要修改为这种多任务共同下载同一文件的形式改动较大。
    其实只是单独实现这个功能并不难,但要加上及调试完成那些进度&速度显示,任务调度控制,下载信息日志记录等所有功能就不是一两个小时的活了,如果不加的话一旦出错就无从知道,在这种网络不稳的情况下靠人品下载几个G的大文件几乎是不可能的。
  2. 当下载停止时,需要保存任务至配置文件,而序列化也是一个较麻烦的活,需要详细测试,一旦反序列化失败就白下了。
  3. 另外,微软提供的链接是一个动态链接,很可能失效(类似那些视频网站的链接),如果下载的链接发生了变化也是一个需要考虑的问题。

由于FlvDownloader就支持任务调度和子任务并发功能,并且有比较完善的信息显示和控制界面。因此,要在短时间内写一个较成熟的工具,需要尽可能复用原来的功能。因此,对于多线程的断点续传我采用了这样的一种方案:

  1. 首先探测文件的总体长度,按固定长度(我这里取的是30m)生成多个子任务。
  2. 每个任务只下载一部分,生成一个独立的文件。
  3. 当任务完成后,根据任务偏移量和长度信息将所有子任务的文件合并为一个完整的文件。
  4. 当任务停止后续传时,子任务开始前首先根据已经下载的部分文件大小重新设置偏移量(如果已经下载部分大小为该任务的总量时,认为任务已经完成,不下载)。

这种方式和前面的迅雷等工具采用的方式相比有如下特点:

  1. 任务分配方式简单。只需要按固定大小划分即可
  2. 任务下载方式简单。每个任务下载方式和以前没有区别,不用修改。由于是独立下载的方式,也不用考虑并发时共享资源所带来的一系列问题。
  3. 不用保存配置文件。子任务是不需要序列化的,即使更新了源链接,重新分配的子任务和以前的是一致的,只要在下载前、时候根据上次下载文件的大小信息即可更新偏移量即可实现续传功能。
  4. 任务调度简单。
    迅雷的调度方式是:线程数是固定的,每个线程在完成自己的任务后还需要分担别的线程的下载任务,从而保证多线程下载。因此这里还牵涉到任务分割和合并的问题,实现起来较为麻烦。
    而我这里的方式是:任务数是固定的(任务数远大于执行线程数),执行线程数是靠并发粒度来控制。可以用PLINQ等相关接口轻易实现。

采用这种方式,只需要继承原来的DownloadTask,覆写子任务的分配接口,在子任务完成(以前就有这个功能,用来合并优酷的分段视频)时合并所有子任务下载的文件即可。

子任务也可以继承原来的DownloadClient,主要修改点如下:

  • 子任务信息中增加Offset和Length数据
  • 下载之前根据如果待下载的文件已经存在,需要更新Offset
  • 发送HttpWebrequest的时候需要将Offset发送至Http的Range头中
  • 下载的时候不需要等整个文件下载完成,只要下载的长度超过(下载多了一点是没关系的)了Length即可

整个修改的工作量不大,设计到实现不到一个小时就改完了。找了个搜狗输入法测试了一下,有一个小bug和一个下载进度的显示问题,改完了后就将其投入了VisualStudio的下载任务中,还是非常顺利的。

当然,我这里只是一种简单的实现,主要是为了下载微软的VisualStudio,如果要实现一个成熟的多线程下载工具还有很多要做的工作,需要考虑很多异常情况。有点跑题,就不多说了。

原文地址:https://www.cnblogs.com/TianFang/p/1667670.html