基于Http协议的多线程断点下载功能

来源:互联网 发布:7zip解压数据错误 编辑:程序博客网 时间:2024/04/28 23:19

    最近,在学习android开发的时候,发现受限于3G网络的网速影响,在下载文件时使用单线程下载特别的慢,在网上看了一些大神的文档,借以总结和修改来实现基于Http协议的多线程断点下载功能。

一、文件下载时http协议的获取与应用

    首先,我们需要知道在http协议下,当用户请求下载文件时,所需要的请求头和请求内容是什么(在这里我使用httpwatch9.1这个工具来获取请求信息),创建一个简单的上传功能的web项目:

其中表单为:

    <formaction="/web/UploadServlet" method="post"enctype="multipart/form-data">

       标题:<input name="title" type="text"><br/>

       时长:<input name="time" type="text"><br/>

       文件:<input type="file" name="file" />

       <inputtype="submit"value=" 提 交 " />

    </form>

    在浏览器中启用httpwatch9.1来监控点击按钮这个时间所产生的http协议(在这里为了方便查看,上传的文件

非常简单):

    POST/web/UploadServlet HTTP/1.1

    Accept:image/jpeg, application/x-ms-application, image/gif, application/xaml+xml,image/pjpeg, application/x-ms-xbap, */*

    Referer:http://localhost:8080/web/

    Accept-Language:zh-CN

    User-Agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0;.NET4.0C; .NET4.0E)

    Content-Type:multipart/form-data; boundary=---------------------------7de3532a30660

    Accept-Encoding:gzip, deflate

    Host:localhost:8080

    Content-Length:403

    Connection:Keep-Alive

    Cache-Control:no-cache

    Cookie:JSESSIONID=220C178EFBAFAD6B785B8E8B8DB8D894


    -----------------------------7de3532a30660

    Content-Disposition:form-data; name="title"

 

    TomAnd Jim

    -----------------------------7de3532a30660

    Content-Disposition:form-data; name="time"

 

    100

    -----------------------------7de3532a30660

    Content-Disposition:form-data; name="file"; filename="1.txt"

    Content-Type:text/plain

 

    successupload file

    -----------------------------7de3532a30660--

    在这里我就不一一解释上面的含义了(其实大家看以下就会明白的),其中重点需要提的就是boundary(分割线)和Content-Length(文本和文件的总长度,包括分割线的长度),Content-Length这个属性的值将在后面子线程任务的划分起到十分重要的作用。

    相应的浏览器将会响应给用户,它的具体信息是:

    HTTP/1.1200 OK

    Server:Apache-Coyote/1.1

    Content-Length:0

    Date:Fri, 10 Oct 2014 15:20:42 GMT

    其中在第一行的返回响应码【200】将是我们判断响应是否成功的重要依据。响应码常用的有这几种:

    404:Not Found 无法找到指定位置的资源。这也是一个常用的应答

    200:OK 一切正常

    206:Partial Content 服务器已经成功处理了部分 GET 请求

    414:Request URI Too Long URI太长

    416:Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头

    500:nternal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求

    503:Service Unavailable 服务器由于维护或者负载过重未能应答

    在这里有200与206才是我们需要的正确的状态。好了,上面内容基本上能够满足上传功能所需的http部分了,在下面的代码实现中将会介绍如何向web服务器发送请求。

二、多线程下载实现原理

    多线程编程的目的,就是"最大限度地利用CPU资源",但是我们在开子线程时又不能太多(根据性能的需求设定子线程个数,如果开的太多,会导致机器的性能下降),我这里以三个子线程为例,通过平均算法来分配每个线程的任务量,其原理图如下:

1、           首先获取网络文件的长度,然后在客户端生成一个与网络文件长度相等的文件

2、开启N条线程,计算每条线程所需下载的数据量,公式如下:

int block = 文件长度 % N == 0 ? 文件长度 / N : 文件长度 / N + 1

3、开启多条线程分别从网络文件的不同位置下载数据,并从本地文件的相同的位置写入数据,需要计算出每条线从文件的什么位置开始下载数据,到什么位置结束

4、 其部分源代码实现为

(1)构造文件下载器

 

(2)文件下载



(3)多线程的下载

三、通过数据库记录下载的断点数据

         在现实的下载中我们经常会遇到暂停、死机、断网等打断下载任务的时间,这样我们就需要设置断点来保存各个线程下载开始现在任务的位置,避免从头开始。这是个简单的问题,我们只需要在打断事件的上一时刻实时更新断点任务即可(创建一个用来保存下载任务数据的数据表即可,使用增删改查的sql语句即可实现)。

四、在Andriod环境下的实现

由于Andriod开发环境的特殊性,我们需要注意两点:

(1)用户的输入事件(点击button, 触摸屏幕....)是由主线程负责处理的,如果主线程处于工作状态,此时用户产生的输入事件如果没能在5秒内得到处理,系统就会报“应用无响应”错误。所以在主线程里不能执行一件比较耗时的工作,否则会因主线程阻塞而无法处理用户的输入事件,导致“应用无响应”错误的出现。耗时的工作应该在子线程里执行)因此在此处定义成员变量来实现子线程下载任务。

(2)UI控件画面的重绘(更新)是由主线程负责处理的,如果在子线程中更新UI控件的值,更新后的值不会重绘到屏幕上,一定要在主线程里更新UI控件的值,这样才能在屏幕上显示出来,不能在子线程中更新UI控件的值。

那么,问题来了,第一点要求我们把任务放在子线程中,但第二点又要求我们将任务放在主线程中,这样是不是矛盾了?呵呵,当时我遇到这个问题特别的头痛,在网上查了一下,发现Andriod的SDK伴我们实现了一个类Handler,来解决这个问题,下面我来介绍一下这个类。

         当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。  如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭".  这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程是线程不安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的. 这个时候,Handler就出现了.,来解决这个复杂的问题 , 由于Handler运行在主线程中(UI线程中),  它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,(里面包含数据)  , 把这些消息放入主线程队列中,配合主线程进行更新UI。

         于是就可以这样来实现了,只需要在主线程中创建Hander对象,通过Android给我们定义的Message对象将子线程中的消息传递给创建的Hander对象,在Handler处理机制中, Handler就会被唤醒,会调用handleMessage这个方法来处理当前线程的消息队列,它的实现代码为:

 

五、总结

         这是我的第一篇博客,有不足之处希望大家多多指正。我在这边博客中所附的代码并不是完整的代码,只是希望我个人对断点下载功能的实现的认识可以给大家有所启发,谢谢。

0 0