importjava.io.File;importjava.io.FileNotFoundException;importjava.io.IOException;importjava.io.InputStream;importjava.io.RandomAccessFile;importjava.net.HttpURLConnection;importjava.net.MalformedURLException;importjava.net.URL;importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;/** Encode:UTF-8
*
* Author:zhiming.xu
*
* 多线程的断点下载程序,根据输入的url和指定线程数,来完成断点续传功能。
*
* 每个线程支负责某一小段的数据下载;再通过RandomAccessFile完成数据的整合。*/
public classMultiTheradDownLoad {private String filepath = null;private String filename = null;private String tmpfilename = null;private int threadNum = 0;private CountDownLatch latch = null;//设置一个计数器,代码内主要用来完成对缓存文件的删除
private long fileLength = 0l;private long threadLength = 0l;private long[] startPos;//保留每个线程下载数据的起始位置。
private long[] endPos;//保留每个线程下载数据的截止位置。
private boolean bool = false;private URL url = null;//有参构造函数,先构造需要的数据
public MultiTheradDownLoad(String filepath, intthreadNum) {this.filepath =filepath;this.threadNum =threadNum;
startPos= new long[this.threadNum];
endPos= new long[this.threadNum];
latch= new CountDownLatch(this.threadNum);
}/** 组织断点续传功能的方法*/
public voiddownloadPart() {
File file= null;
File tmpfile= null;
HttpURLConnection httpcon= null;//在请求url内获取文件资源的名称;此处没考虑文件名为空的情况,此种情况可能需使用UUID来生成一个唯一数来代表文件名。
filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath
.contains("?") ? filepath.lastIndexOf('?') : filepath.length());
tmpfilename= filename + "_tmp";try{
url= newURL(filepath);
httpcon=(HttpURLConnection) url.openConnection();
setHeader(httpcon);
fileLength= httpcon.getContentLengthLong();//获取请求资源的总长度。
file= newFile(filename);
tmpfile= newFile(tmpfilename);
threadLength= fileLength / threadNum;//每个线程需下载的资源大小。
System.out.println("fileName: " + filename + " ," + "fileLength= "
+ fileLength + " the threadLength= " +threadLength);if (file.exists() && file.length() ==fileLength) {
System.out
.println("the file you want to download has exited!!");return;
}else{
setBreakPoint(startPos, endPos, tmpfile);
ExecutorService exec=Executors.newCachedThreadPool();for (int i = 0; i < threadNum; i++) {
exec.execute(newDownLoadThread(startPos[i], endPos[i],this, i, tmpfile, latch));
}
latch.await();//当你的计数器减为0之前,会在此处一直阻塞。
exec.shutdown();
}
}catch(MalformedURLException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}catch(InterruptedException e) {
e.printStackTrace();
}if (file.length() ==fileLength) {if(tmpfile.exists()) {
System.out.println("delect the temp file!!");
tmpfile.delete();
}
}
}/** 断点设置方法,当有临时文件时,直接在临时文件中读取上次下载中断时的断点位置。没有临时文件,即第一次下载时,重新设置断点。
*
* rantmpfile.seek()跳转到一个位置的目的是为了让各个断点存储的位置尽量分开。
*
* 这是实现断点续传的重要基础。*/
private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
RandomAccessFile rantmpfile= null;try{if(tmpfile.exists()) {
System.out.println("the download has continued!!");
rantmpfile= new RandomAccessFile(tmpfile, "rw");for (int i = 0; i < threadNum; i++) {
rantmpfile.seek(8 * i + 8);
startPos[i]=rantmpfile.readLong();
rantmpfile.seek(8 * (i + 1000) + 16);
endPos[i]=rantmpfile.readLong();
System.out.println("the Array content in the exit file: ");
System.out.println("thre thread" + (i + 1) + " startPos:"
+ startPos[i] + ", endPos: " +endPos[i]);
}
}else{
System.out.println("the tmpfile is not available!!");
rantmpfile= new RandomAccessFile(tmpfile, "rw");//最后一个线程的截止位置大小为请求资源的大小
for (int i = 0; i < threadNum; i++) {
startPos[i]= threadLength *i;if (i == threadNum - 1) {
endPos[i]=fileLength;
}else{
endPos[i]= threadLength * (i + 1) - 1;
}
rantmpfile.seek(8 * i + 8);
rantmpfile.writeLong(startPos[i]);
rantmpfile.seek(8 * (i + 1000) + 16);
rantmpfile.writeLong(endPos[i]);
System.out.println("the Array content: ");
System.out.println("thre thread" + (i + 1) + " startPos:"
+ startPos[i] + ", endPos: " +endPos[i]);
}
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally{try{if (rantmpfile != null) {
rantmpfile.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}/** 实现下载功能的内部类,通过读取断点来设置向服务器请求的数据区间。*/
class DownLoadThread implementsRunnable {private longstartPos;private longendPos;private MultiTheradDownLoad task = null;private RandomAccessFile downloadfile = null;private intid;private File tmpfile = null;private RandomAccessFile rantmpfile = null;private CountDownLatch latch = null;public DownLoadThread(long startPos, longendPos,
MultiTheradDownLoad task,intid, File tmpfile,
CountDownLatch latch) {this.startPos =startPos;this.endPos =endPos;this.task =task;this.tmpfile =tmpfile;try{this.downloadfile = new RandomAccessFile(this.task.filename,"rw");this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw");
}catch(FileNotFoundException e) {
e.printStackTrace();
}this.id =id;this.latch =latch;
}
@Overridepublic voidrun() {
HttpURLConnection httpcon= null;
InputStream is= null;int length = 0;
System.out.println("the thread " + id + " has started!!");while (true) {try{
httpcon=(HttpURLConnection) task.url.openConnection();
setHeader(httpcon);//防止网络阻塞,设置指定的超时时间;单位都是ms。超过指定时间,就会抛出异常
httpcon.setReadTimeout(20000);//读取数据的超时设置
httpcon.setConnectTimeout(20000);//连接的超时设置
if (startPos
httpcon.setRequestProperty("Range", "bytes=" +startPos+ "-" +endPos);
System.out
.println("Thread " +id+ " the total size:---- "
+ (endPos -startPos));
downloadfile.seek(startPos);if (httpcon.getResponseCode() !=HttpURLConnection.HTTP_OK&& httpcon.getResponseCode() !=HttpURLConnection.HTTP_PARTIAL) {this.task.bool = true;
httpcon.disconnect();
downloadfile.close();
System.out.println("the thread ---" +id+ " has done!!");
latch.countDown();//计数器自减
break;
}
is= httpcon.getInputStream();//获取服务器返回的资源流
long count = 0l;byte[] buf = new byte[1024];while (!this.task.bool && (length = is.read(buf)) != -1) {
count+=length;
downloadfile.write(buf,0, length);//不断更新每个线程下载资源的起始位置,并写入临时文件;为断点续传做准备
startPos +=length;
rantmpfile.seek(8 * id + 8);
rantmpfile.writeLong(startPos);
}
System.out.println("the thread " +id+ " total load count: " +count);//关闭流
is.close();
httpcon.disconnect();
downloadfile.close();
rantmpfile.close();
}
latch.countDown();//计数器自减
System.out.println("the thread " + id + " has done!!");break;
}catch(IOException e) {
e.printStackTrace();
}finally{try{if (is != null) {
is.close();
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}/** 为一个HttpURLConnection模拟请求头,伪装成一个浏览器发出的请求*/
private voidsetHeader(HttpURLConnection con) {
con.setRequestProperty("User-Agent","Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
con.setRequestProperty("Accept-Encoding", "aa");
con.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
con.setRequestProperty("Keep-Alive", "300");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("If-Modified-Since","Fri, 02 Jan 2009 17:00:05 GMT");
con.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
con.setRequestProperty("Cache-Control", "max-age=0");
con.setRequestProperty("Referer","http://www.skycn.com/soft/14857.html");
}
}