Java多线程下载网络资源

本文解析了HTTP1.1协议中的Range和Content-Range头部字段如何实现断点续传功能,通过实例演示了如何利用这些字段进行多线程下载,并提供了一个简单的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就可以了。下载速率统计是统计下载量和下载的耗时,最后计算得出速率。




1.得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致 a)获得访问网络地址 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout iii.setReadTimeout d)判断是否响应成功 e)获取文件长度(getContentLength()) f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)设置临时文件与服务器文件大小一致(setLength()) h)关闭临时文件 2.计算出每个线程下载的大小(开始位置,结束位置) a)计算出每个线程下载的大小 b)for循环,计算出每个线程的开始、结束位置 c)最后一个线程处理 3.每创建好一次就要开启线程下载 a)构造方法 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout d)判断是否响应成功(206) e)获取每个线程返回的流对象 f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)指定开始位置 h)循环读取 i.保存每个线程下载位置 ii.记录每次下载位置 iii.关闭临时记录位置文件 iv.随机本地文件写入 v.记录已下载大小 i)关闭临时文件 j)关闭输入流 4.为了杀死线程还能继续下载的情况下,从本地文件上读取已经下载文件的开始位置 a)创建保存记录结束位置的文件 b)读取文件 c)将流转换为字符 d)获取记录位置 e)把记录位置赋给开始位置 5.当你的n个线程都下载完毕的时候我进行删除记录下载位置的缓存文件 a)线程下载完就减去 b)当没有正在运行的线程时切文件存在时删除文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值