Java多线程下载网络资源

时间:2022-11-26 10:29:39

先了解下协议:

声明,以下内容来自http://www.cnblogs.com/pen-ink/articles/1828230.html

HTTP1.1协议(RFC2616)中定义了断点续传相关的HTTP头 Range和Content-Range字段

3.12  Range Units

HTTP/1.1 allows a client to request that only part (a range of) the response entity be included within the response.
HTTP/1.1 uses range units in the Range (section 14.35) and Content-Range(section 14.16) header fields. An
entity can be broken down into subranges according to various structural units.
      range-unit       = bytes-unit | other-range-unit
      bytes-unit       = "bytes"
      other-range-unit = token
The only range unit defined by HTTP/1.1 is “bytes”. HTTP/1.1 implementations MAY ignore ranges specified using
other units. HTTP/1.1 has been designed to allow implementations of applications that do not depend on knowledge
of ranges.

注:以下文章转载自:http://blog.chinaunix.net/u3/94343/showart_1891855.html

假设你要开发一个多线程下载工具,你会自然的想到把文件分割成多个部分,比如4个部分,然后创建4个线程,每个线程负责下载一个部分,如果文件大小为403个byte,那么你的分割方式可以为:0-99 (前100个字节),100-199(第二个100字节),200-299(第三个100字节),300-402(最后103个字节)。

      分割完成,每个线程都明白自己的任务,比如线程3的任务是负责下载200-299这部分文件,现在的问题是:线程3发送一个什么样的请求报文,才能够保证只请求文件的200-299字节,而不会干扰其他线程的任务。这时,我们可以使用HTTP1.1的Range头。Range头域可以请求实体的一个或者多个子范围,Range的值为0表示第一个字节,也就是Range计算字节数是从0开始的:
    表示头500个字节:Range: bytes=0-499
    表示第二个500字节:Range: bytes=500-999
    表示最后500个字节:Range: bytes=-500
    表示500字节以后的范围:Range: bytes=500-
    第一个和最后一个字节:Range: bytes=0-0,-1
    同时指定几个范围:Range: bytes=500-600,601-999
所以,线程3发送的请求报文必须有这一行:
    Range: bytes=200-299

     服务器接收到线程3的请求报文,发现这是一个带有Range头的GET请求,如果一切正常,服务器的响应报文会有下面这行:
HTTP/1.1 206 OK
表示处理请求成功,响应报文还有这一行
Content-Range: bytes 200-299/403
斜杠后面的403表示文件的大小,通常Content-Range的用法为:
     . The first 500 bytes:
     Content-Range: bytes 0-499/1234

     . The second 500 bytes:
     Content-Range: bytes 500-999/1234

     . All except for the first 500 bytes:
     Content-Range: bytes 500-1233/1234

     . The last 500 bytes:
     Content-Range: bytes 734-1233/1234


写了个简单的demo,demo中实现了以下几项

1、连接网络资源、获取资源信息、创建保存文件

2、分割下载任务、启动下载线程

3、实现断点恢复功能

4、下载速率统计

源码如下:

测试类

public class DownloadTest {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String url = "http://pic17.nipic.com/20111102/3707281_235344313129_2.jpg";
DownLoadManager manager = new DownLoadManager(url);
manager.startDownload();
}

}

下载控制器

/*
* 下载控制器,由这里接收下载的任务,并进行分发并启动各个子线程
* 同时创建最终保存文件
* */
public class DownLoadManager {
private String downloadUrl;//下载的url地址

public DownLoadManager(String downloadUrl) {
super();
this.downloadUrl = downloadUrl;
}

public void startDownload(){
try {
//创建下载的url和保存文件
URL url = new URL(downloadUrl);
URLConnection connection = url.openConnection();
int contentLength = connection.getContentLength();
System.out.println("Download content length is: " + contentLength);
File file = new File("b2.jpg");
System.out.println("file path is: " + file.getAbsolutePath());

//分割下载任务,启动下载线程
long sublen = contentLength/3;
for(int i=0; i<3; i++){
long starPos = sublen * i;
long endPos = sublen *(i + 1) -1;
DownloadThread thread = new DownloadThread(starPos, endPos, file, url);
thread.start();
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

下载线程

/*
* 下载线程,负责下载文件的某一个部分,完成后将其写入文件
* */
public class DownloadThread extends Thread {

private long starPos;//下载的开始位置
private long endPos;//下载的结束位置
private File saveFile;//文件保存
private URL url;//下载的URL
private long curPos;//当前下载的位置

public DownloadThread(long starPos, long endPos, File saveFile, URL url) {
super();
this.starPos = starPos;
this.endPos = endPos;
this.saveFile = saveFile;
this.url = url;
curPos = starPos;
}

@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("Download " + starPos + " - " + endPos + " start!");
RandomAccessFile fos = null;
byte[] buf = new byte[256];
URLConnection conn;
BufferedInputStream bis = null;
try {
//创建一个新的连接,设置下载的开始和结束位置
conn = url.openConnection();
conn.setAllowUserInteraction(true);
conn.setRequestProperty("Range", "bytes=" + starPos + "-" + endPos);

//获取随机读取的下载文件模式
fos = new RandomAccessFile(saveFile, "rw");
fos.seek(starPos);

//从网络流中读取数据,并写入到保存文件中
bis = new BufferedInputStream(conn.getInputStream());
while(curPos < endPos){
int len = bis.read(buf, 0, 256);
if(len == -1){
break;
}
fos.write(buf, 0, len);
curPos = curPos + len;
}
System.out.println("Download " + starPos + " - " + endPos + " finish!");
bis.close();
fos.close();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{

}

}

}

本文实现了多线程下载,断点下载恢复和下载速率统计没有实现,其思想均比较简单。断点恢复是通过记录下载的curPos(线程当前的下载点),当线程重新启动时,将Range的starPos设置为上次的curPos就可以了。下载速率统计是统计下载量和下载的耗时,最后计算得出速率。