多线程断点下载原理及其Java实现

 金诚   2017-02-24 19:02   209 人阅读  0 条评论

原理

多线程下载原理是通过使用多个线程去争取服务器的更多资源。

 

断点原理是利用Http协议请求头中的Accept-RangesRangeContent-Range对请求进行标识。

1. HTTP 范围请求,需要 HTTP/1.1 及之上支持,如果双端某一段低于此版本,则认为不支持。

2. 通过响应头中的 Accept-Ranges 来确定是否支持范围请求。

3. 通过在请求头中添加 Range 这个请求头,来指定请求的内容实体的字节范围。

4. 在响应头中,通过 Content-Range 来标识当前返回的内容实体范围,并使用 Content-Length 来标识当前返回的内容实体范围长度。

5. 在请求过程中,可以通过 If-Range 来区分资源文件是否变动,它的值来自 ETag 或者 Last-Modifled。如果资源文件有改动,会重新走下载流程。

 

否支持范围请求

HTTP 本身是一种无状态的“松散”协议,而在经历了很多版本的迭代之后,只在 HTTP/1.1(RFC2616) 之上,才支持范围请求。所以如果客户端或者服务端两端的某一端低于 HTTP/1.1,我们就不应该使用范围请求的功能。

而在 HTTP/1.1 中,很明确的声明了一个响应头部 Access-Ranges 来标记是否支持范围请求,它只有一个可选参数 bytes

例如这里给了一个 MP4 的响应头,可以看到它是有 Accept-Ranges:bytes 来标记的,有此标记标识当前资源支持范围请求。

使用范围请求

如果已经确定双端都支持范围请求,我们就可以在请求资源的时候使用它。

所有的文件最终都是存储在磁盘或者内存中的字节,对于待操作的文件可以将其以字节为单位分割。这样只需要 HTTP 支持请求该文件从 n 到 n+x 这个范围内的资源,就可以实现范围请求了。

HTTP/1.1 中定义了一个 Ranges 的请求头,来指定请求实体的范围。它的范围取值是在 0 - Content-Length 之间,使用 - 分割。。

例如已经下载了 1000 bytes 的资源内容,想接着继续下载之后的资源内容,只要在 HTTP 请求头部,增加 Ranges:bytes=1000- 就可以了。

Range 还有几种不同的方式来限定范围,可以根据需要灵活定制:

1. 500-1000:指定开始和结束的范围,一般用于多线程下载。

2. 500- :指定开始区间,一直传递到结束。这个就比较适用于断点续传、或者在线播放等等。

3. -500:无开始区间,只意思是需要最后 500 bytes 的内容实体。

4. 100-300,1000-3000:指定多个范围,这种方式使用的场景很少,了解一下就好了。

HTTP 协议是一种双边协商的协议,既然请求头部已经确定是使用 Ranges 了,还有响应头部中,也需要使用 Content-Ragne 这个响应头来标记响应的实体内容范围。

Content-Range 的格式也很清晰,首先标记它的单位是 bytes 然后标记当前传递的内容实体范围和总长度。

原理图

原理图1
原理图2
原理图3

原理解析

接着就是我们都知道文件存储是有顺序的,当存储的二进制数据0和1发生了变化的时候,文件就发生了翻天覆地的变化.所以我们必须保证下载的东西存储顺序是原来一样的,一一对应.我们的思路每个线程负责一个区域的局部下载,所以我们把请求的数据分块,并把一个文件分块(这样就不必每次都需要请求一个完整的文件了).很幸运哈,http协议有个RANGE属性字段,就是可以设置请求数据的区域范围.而且,Java也为我们提供了一个RandomAccessFile类,它可以让我们按需要去读写改某个区域.下面看图时间到...

原理图3

计算

下面就是分块内容了,首先我们可以获取文件的大小fileSize,然后获取每个线程应该分配的大小eachSize = fileSize / THREAD_COUNT.有了下载大小还不可以,我们还需要计算从哪里开始startIndex下载,和从从哪里结束下载endIndex(用于确定请求范围),考虑下面公式,如果有线程0 1 2.

那么

  • 0线程:
  • startIndex = 0 * eachSize
  • endIndex = 1 * eachSize - 1;
  • 1线程:
  • startIndex = 1 * eachSize
  • endIndex = 2 * eachSize - 1;
  • 2线程:
  • startIndex = 2 * eachSize
  • endIndex = 3 * eachSize - 1;
  • 总结:
  • startIndex = i * eachSize;
  • endIndex = (i + 1) * eachSize - 1;

大概就能这样写?显然,最后一个线程计算的公式是有问题的,因为fileSize / THREAD_COUNT可能是有余数的,这样的话我们的下载就不完整了,那么怎么办.很简单 最后一个线程的结束就是整个文件的大小嘛...

编码(带注释)


 

本文地址:http://www.yangchaofan.cn/archives/2394
版权声明:本文为原创文章,版权归 金诚 所有,欢迎分享本文,转载请保留出处!

说点什么

avatar
  Subscribe  
提醒