Java多线程下载网络资源

先了解下协议:

声明,以下内容来自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就可以了。下载速率统计是统计下载量和下载的耗时,最后计算得出速率。




阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页