TechToy

follow your principle

多线程断点续传之下载

| Comments

年底了,本该是个收官的时节,却因一个新项目搞得忙碌不堪,博客也一直无心顾及。这会儿忙里偷闲,正好整理整理最近的学习心得,也算做个年终总结吧。

大概一个月前,偶然看到一篇博文,博主面试一位新员工,问断点续传该怎么实现。很不幸,那个被面试的哥们没答出来。我当时就在想,我要碰到这个估计也挂了,于是有心想了解一下这方面的知识。 我一向推崇不知道就搜索,关于断点续传,网上有很多资料,其中有一篇博文总结的很不错,摘抄如下:

多线程下载

多线程下载的基本思想就是把要下载的文件按下载线程的个数N,划分为N块,每个线程负责下载一块。具体如何下载文件的块,不同的协议有点差别:

  1. Http多线程下载
    Http协议支持Range关键字,可以用Range来指定要下载的范围,如下所示(假设要下载的文件总大小为1500bytes,用三个线程下,每个线程下载500bytes)

首500字节(字节位移0-499)

    Range: bytes=0-499

次500字节(字节位移500-999)

    Range: bytes=500-999

后500字节(字节位移1000-1499)

    Range: bytes=1000-1499,或者Range: bytes=1000-

需要注意,Http请求头中加了Range字段后,请求成功的状态码206(部分内容),不再是200。

  1. Ftp多线程下载
    Ftp协议支持REST和RETR指令,可以用这两个指令来指定要下载起始位置,如下所示(假设要下载的文件总大小为1500bytes,用三个线程下,每个线程下载500bytes, 文件名为file)

首500字节: 首先用REST 0设置从第0个字节开始下载,然后用 RETR file指定要下载的文件,启动数据接收,开始下载。
次500字节和最后500字节的做法和上面一样,唯一的区别在于REST指定的起始位置不一样,次500字节为REST 500,最后500字节为REST 1000。

需要注意,Ftp不能像Http那样,指定要下载的范围,只能指定要下载的起位置。因此,应用需要自己把握下载了多少个字节,比如说,要下载中间500个字节,用REST设置完起始位置,开始下载后,如果应用判断到接收的数据已大于等于500个字节,就需要停止下载。

断点续传

断点续传的基本思想就是记住上次连接断开时已经下载的字节数N,然后本次下载的时候,从第N+1个字节开始下载,如果读者朋友对第一部分中讲的多线程下载已经理解的话,那么如果进行断点续传便是水到渠成的事情,下面我只做简单的说明,假设已经下载的字节为N:

Http: Range: bytes=N- (注意字节位移是从0开始的,因此第N+1个字节的位移是N)
Ftp: a. REST N, b.RETR file

这段文字先介绍了多线程下载的原理,然后是断点续传。在我看来,这两个原理是一样的。博文中除了提到了Http协议外,还讲了Ftp协议多线程断点续传。对于Ftp协议我了解不多,不作多谈。这段时间正好有个机会需要实现Http的多线程断点续传,说说我实现过程中的一些心得体会吧。

能否断点续传,不是只要客户端说支持就可以的,还要服务器支持。在你请求下载文件时,服务器回应的报文中,如果Http header包含Accept-Ranges: bytes,就说明服务器支持按偏移下载文件,也就支持多线程断点续传了,当然多线程、断点续传都需要在客户端来实现。

另外,Accept-Ranges: bytes中的bytes说明偏移的单位是bytes,不是KiloBytes,更不是MegaBytes。那么客户端怎么指点偏移呢?很简单,只要在下载请求报文的header中包含Range字段,具体格式:

Range: bytes=xxxx- 

这儿只指定了起始偏移,并未指定结束偏移,我用WireShark抓旋风的下载请求时,它就是使用这样的格式。不过,同事说,迅雷也指定了结束偏移,哪种用法更好,暂时我还没有结论。不过,我在实现过程中发现,如果一个完整的文件,分割成多块之后,每块只指定起始偏移,没有结束偏移,其效果跟指定结束偏移一样。还拿上面的1500bytes的文件来举例,三个线程下载,每个线程的请求报文的Range格式如下:

Range: bytes=0-
Range: bytes=500-
Range: bytes=1000-

使用此下载请求,最后每一个请求的回复都只读取到了500bytes的内容,跟我最开始认为的1500,1000,500不一样,我不知道这是后台服务器比较智能,将整个文件的下载统筹规划了一下,还是说Http协议本身就是这样规定的。不过,我猜测应该是前一种,虽然没有详细看过Http协议,但我觉得协议不太可能有这个规定。另外,我测试的后台服务器是Nginx,其他服务器是不是也这样,暂时未知。

其实到此为止,我只说了续传的一半,续传中的另一半–上传还没有涉及,后面有空会再说说上传。

PS:纠正一下关于Range的错误,如果只指定起始便宜,不指定终结偏移,则应该是默认从起始偏移到文件结尾。

Comments