自己写的一个简单的迅雷下载支持断点续传

原创 2015年08月20日 11:08:28

当我学习了网络线程,就自己仿照迅雷下载写了一个下载器,支持断点续传

我用的是SWT插件做的界面 

界面

package com.yc.xunlei;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;

public class Xunlei {

	protected Shell shell;
	private Text txt;

	private Combo combo;
	private long sum;
	private ProgressBar progressBar;

	private File downLoadFile;

	private Text text;

	private Map<String, List<ThreadInfo>> threadInfos = new HashMap<String, List<ThreadInfo>>();

	private Label label_2;

	private String key;

	DownLoadUtils dlu;

	/**
	 * Launch the application.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			Xunlei window = new Xunlei();
			window.open();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Open the window.
	 */
	public void open() {
		Display display = Display.getDefault();
		createContents();
		shell.open();
		shell.layout();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
	}

	/**
	 * Create contents of the window.
	 */
	protected void createContents() {
		shell = new Shell();
		shell.setSize(610, 468);
		shell.setText("\u8FC5\u96F7\u4E0B\u8F7D");

		Label lblUrl = new Label(shell, SWT.NONE);
		lblUrl.setBounds(26, 36, 40, 15);
		lblUrl.setText("url:");

		txt = new Text(shell, SWT.BORDER);
		txt
				.setText("http://dlsw.baidu.com/sw-search-sp/soft/3a/12350/QQ_v7.3.15056.0_setup.1435111953.exe");
		txt.setBounds(72, 33, 520, 18);

		Button button = new Button(shell, SWT.NONE);

		button.setBounds(127, 201, 72, 22);
		button.setText("\u4E0B\u8F7D");

		Button button_1 = new Button(shell, SWT.NONE);

		button_1.setBounds(236, 201, 72, 22);
		button_1.setText("\u6682\u505C");

		progressBar = new ProgressBar(shell, SWT.NONE);
		progressBar.setBounds(72, 245, 461, 17);

		Label label = new Label(shell, SWT.NONE);
		label.setBounds(26, 83, 42, 25);
		label.setText("\u7EBF\u7A0B\u6570:");

		combo = new Combo(shell, SWT.NONE);
		combo.setItems(new String[] { "5", "6", "7", "8", "9", "10" });
		combo.setBounds(72, 83, 87, 20);
		combo.select(0);

		Label label_1 = new Label(shell, SWT.NONE);
		label_1.setBounds(26, 141, 54, 18);
		label_1.setText("\u4FDD\u5B58\u4F4D\u7F6E:");

		text = new Text(shell, SWT.BORDER);
		text.setBounds(89, 141, 296, 18);

		text.setText(System.getProperty("user.home"));

		label_2 = new Label(shell, SWT.NONE);
		label_2.setBounds(127, 281, 342, 31);

		// 暂停的方法
		button_1.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				if (dlu != null) {
					dlu.stop();
				}

			}
		});

		/**
		 * a. 创建要下载的文件到本地磁盘 b. 设置界面上progressbar的总长度 c. 再开始下载
		 * d.修改System.out.println("已经下载了:"+ sum+"个字节");为 progressbar的设置
		 */
		button.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				sum = 0; // 每次点ji开始时,要将sum赋初值为0

				String urlString = txt.getText().trim();
				int threadSize = Integer.parseInt(combo.getText());
				String savePath = text.getText();
				try {
					dlu = new DownLoadUtils(threadSize, urlString, savePath);

					downLoadFile = dlu.getDownLoadFile();
					key = threadSize + "_" + urlString + "_"	+ downLoadFile.getAbsolutePath();
					// 设置界面上progressbar的总长度
					progressBar.setMaximum((int) downLoadFile.length());
					long allThreadDownLoadedSize = dlu
							.getAllThreadDownLoadedSize(key);

					label_2.setText("总长度:" + (int) downLoadFile.length()
							+ "/已下载的长度" + allThreadDownLoadedSize);

					sum += allThreadDownLoadedSize;

					 dlu.downLoad(downLoadFile, urlString,
							threadSize, new OnSizeChangeListener() {
								public void onSizeChange(long downLoadSize) {
									sum += downLoadSize;
									Display.getDefault().asyncExec(
											new Runnable() {
												@Override
												public void run() {
													progressBar
															.setSelection((int) sum);
													label_2
															.setText("总长度:"
																	+ (int) downLoadFile
																			.length()
																	+ "/已下载的长度"
																	+ sum);
												}
											});
									if (sum >= downLoadFile.length()) {
										dlu.stop();
										Display.getDefault().asyncExec(
												new Runnable() {
													@Override
													public void run() {
														MessageBox mb = new MessageBox(
																shell, SWT.NO);
														mb.setText("下载完毕");
														mb.setMessage("OK");
														mb.open();
													}
												});

										

									}

								}

							});

				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});

	}
}

实体类 bean(ThreadInfo)

package com.yc.xunlei;

import java.io.Serializable;

public class ThreadInfo implements Serializable {
	private static final long serialVersionUID = -8664947024042932015L;
	private int threadId;
	private long downLoadSize;

	public int getThreadId() {
		return threadId;
	}

	public void setThreadId(int threadId) {
		this.threadId = threadId;
	}

	public long getDownLoadSize() {
		return downLoadSize;
	}

	public void setDownLoadSize(long downLoadSize) {
		this.downLoadSize = downLoadSize;
	}

	public ThreadInfo(int threadId, long downLoadSize) {
		super();
		this.threadId = threadId;
		this.downLoadSize = downLoadSize;
	}

	public ThreadInfo() {
		super();
	}

	@Override
	public String toString() {
		return threadId + "\t" + downLoadSize;
	}

}

回调接口. 用来通知主线程下载的数据量...

package com.yc.xunlei;

/**
 * 回调接口. 用来通知主线程下载的数据量...
 * @author Administrator
 *
 */
public interface OnSizeChangeListener {
	public void onSizeChange(   long downLoadSize  );
}

下载任务类类

package com.yc.xunlei;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;



public class DownLoadTask implements Runnable {
	private File downLoadFile;
	private String urlString;
	private long startPosition;
	private long endPosition;
	private int threadId;
	private OnSizeChangeListener onSizeChangeListener ;
	
	private boolean flag=true;
	
	private long downLoadedSize=0;
	private long downLoadSizePerThread;
	
	
	
	public long getDownLoadedSize() {
		return downLoadedSize;
	}

	public int getThreadId() {
		return threadId;
	}

	public void stop(   ){
		this.flag=false;
		try {
			this.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

	public DownLoadTask(File downLoadFile, String urlString,
			long startPosition, long endPosition, int threadId,   OnSizeChangeListener onSizeChangeListener,  long downLoadSizePerThread ) {
		this.downLoadFile = downLoadFile;
		this.urlString = urlString;
		this.startPosition = startPosition;
		this.endPosition = endPosition;
		this.threadId = threadId;
		this.onSizeChangeListener= onSizeChangeListener ;
		this.downLoadSizePerThread=downLoadSizePerThread;
	}

	public void run() {
		downLoadedSize=     startPosition-   threadId*downLoadSizePerThread;
		try {
			URL url = new URL(urlString);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			con.setRequestMethod("GET"); // 请求头
			con.setConnectTimeout(5 * 1000); // 请求过期的时间
			con.setRequestProperty("Connection", "Keep-alive");
			// TODO:发出协议,指定Range
			con.setRequestProperty("Range", "bytes=" + startPosition + "-"
					+ endPosition);
			RandomAccessFile raf = new RandomAccessFile(downLoadFile, "rw");
			// TODO: raf不能从第0个字节写入,而必须从 startPosition位置写入 ,问题来了,如何控制raf从指定位置写入呢?
			raf.seek(startPosition);

			InputStream iis = con.getInputStream();
			byte[] bs = new byte[1024];
			int length = -1;
			while ((length = iis.read(bs, 0, bs.length)) != -1) {
				raf.write(bs, 0, length);
				
				if(  this.onSizeChangeListener!=null  ){
					onSizeChangeListener.onSizeChange(    length   );
				}
				this.downLoadedSize+= length;
				//标量,用于控制线程的暂停
				if(   !flag){
					break;
				}
			}
			iis.close();
			con.disconnect();
			raf.close();
			 System.out.println(threadId + "号线程下载完成,范围" + startPosition + "至"
			 + endPosition);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
下载 Util类

package com.yc.xunlei;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DownLoadUtils {
	private List<DownLoadTask> downLoadTasks = new ArrayList<DownLoadTask>();
	private long allThreaddownLoadedSize;
	private String key;
	private Map<String, List<ThreadInfo>> map;
	private int threadSize;
	private String urlString;
	private String savePath;
	private File downLoadFile;
	private List<Thread> threads=new ArrayList<Thread>();

	public File getDownLoadFile() {
		return this.downLoadFile;
	}

	public DownLoadUtils(int threadSize, String urlString, String savePath)
			throws IOException {
		this.threadSize = threadSize;
		this.urlString = urlString;
		this.savePath = savePath;
		key = threadSize + "_" + urlString + "_" + savePath + File.separator
				+ getDownLoadFileName(urlString);
		downLoadFile = createDownLoadFile(urlString, savePath);
		map=getDownLoadedThreadInfoMapFromTmpFile();
		System.out.println("读取到的数据:"+ map );
		if( map==null){
			map=new HashMap< String, List<ThreadInfo>>();
		}
	}

	public long getAllThreadDownLoadedSize(String key) {
		allThreaddownLoadedSize = 0;
		if (map != null && map.size() > 0) {
			List<ThreadInfo> list = map.get(key);
			for (ThreadInfo ti : list) {
				allThreaddownLoadedSize += ti.getDownLoadSize();
			}
		}
		return allThreaddownLoadedSize;
	}

	public void stop() {
		if (downLoadTasks != null && downLoadTasks.size() > 0) {
			for (int i=0;i<threads.size();i++) {
				downLoadTasks.get(i).stop();
				Thread t=threads.get(i);
				t=null;
			}
		}
		if (isDownLoadFinish()) {
			map.remove(key);
		}
		recordDownLoadThreadInfo();
	}

	private void recordDownLoadThreadInfo() {
		ObjectOutputStream oos = null;
		try {
			List<ThreadInfo> list = new ArrayList<ThreadInfo>();
			for (DownLoadTask dlt : downLoadTasks) {
				// 在DownLoadTask中增加一个属性,表示这个线程下载的线据量, 累加
				// 当暂停时,在这里,调用 getxxx方法得到空上线程下载的量.
				// 操作磁盘记录
				ThreadInfo ti = new ThreadInfo();
				ti.setThreadId(dlt.getThreadId());
				ti.setDownLoadSize(dlt.getDownLoadedSize());
				list.add(ti);
			}
			map.put(key, list);
			System.out.println( "保存的数据:"+map );
			FileOutputStream fos = new FileOutputStream(new File(System
					.getProperty("user.home"), "data.tmp"));
			oos = new ObjectOutputStream(fos);
			oos.writeObject(map);
			oos.flush();
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			try {
				if (oos != null) {
					oos.close();
				}
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

	private boolean isDownLoadFinish() {
		// 计算当前下载的总长度
		long downSize = 0;
		for (DownLoadTask dlt : downLoadTasks) {
			downSize += dlt.getDownLoadedSize();
		}
		// 文件总长度
		long totalLength = this.downLoadFile.length();
		if (downSize >= totalLength) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 多线程下载的实现
	 * 
	 * @param downLoadFile
	 * @param urlString
	 * @param threadSize
	 * @throws IOException
	 */
	public List<DownLoadTask> downLoad(File downLoadFile, String urlString,
			int threadSize, OnSizeChangeListener onSizeChangeListener)
			throws IOException {
		long startPosition = 0; // 当前线程的起始位置
		long endPosition = 0; // 当前线程的结束
		// 获取每个线程要下载的长度
		long downLoadSizePerThread = getDownLoadSizePerThread(downLoadFile
				.length(), threadSize);

		// TODO: 1. 拼接map的键 2. 到 磁盘上找是否有map,map中是否有这个键,
		// 3. 有则取出值 4. 循环来计算这个起始位置
		key = threadSize + "_" + urlString + "_"
				+ downLoadFile.getAbsolutePath();
		Map<String, List<ThreadInfo>> threadInfos = getDownLoadedThreadInfoMapFromTmpFile();
		List<ThreadInfo> list = new ArrayList<ThreadInfo>();
		if (threadInfos != null) {
			list = threadInfos.get(key);
		}
		for (int i = 0; i < threadSize; i++) {
			if (list.size()>0 && list.get(i) != null) {
				// 起始位置
				startPosition = i * downLoadSizePerThread
						+ list.get(i).getDownLoadSize();
				allThreaddownLoadedSize += list.get(i).getDownLoadSize();
			} else {
				// 起始位置
				startPosition = i * downLoadSizePerThread;
			}
			// 终点位置
			endPosition = (i + 1) * downLoadSizePerThread - 1;
			// TODO:这个地方必须取得所有的DownLoadTask的实例, 返回给主界面,再调用
			// DownLoadTask中的某个方法,来设置标量.
			downLoadTasks.add(new DownLoadTask(downLoadFile, urlString,
					startPosition, endPosition, i, onSizeChangeListener,   downLoadSizePerThread ));
		}

		if (allThreaddownLoadedSize < downLoadFile.length()) {
			for (int i = 0; i < threadSize; i++) {
				Thread t=new Thread(downLoadTasks.get(i) );
				threads.add( t );
				t.start();
			}
		}
		return downLoadTasks;
	}

	/**
	 * 读取临时文件中存的已经下载的线程的信息
	 * 
	 * @return
	 */
	private Map<String, List<ThreadInfo>> getDownLoadedThreadInfoMapFromTmpFile() {
		Map<String, List<ThreadInfo>> threadInfos = null;
		ObjectInputStream ois = null;
		FileInputStream fis = null;
		try {
			File f = new File(System.getProperty("user.home"), "data.tmp");
			if (!f.exists()) {
				return null;
			}
			fis = new FileInputStream(f);

			ois = new ObjectInputStream(fis);

			threadInfos = (Map<String, List<ThreadInfo>>) ois.readObject();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (ois != null)
					ois.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (fis != null)
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
		return threadInfos;
	}

	/**
	 * 计算每个线程要下载的长度
	 * 
	 * @param fileLength
	 * @param threadSize
	 * @return
	 */
	public long getDownLoadSizePerThread(long fileLength, int threadSize) {
		long downLoadSizePerThread = 0;
		downLoadSizePerThread = fileLength % threadSize == 0 ? fileLength
				/ threadSize : fileLength / threadSize + 1;
		return downLoadSizePerThread;
	}

	/**
	 * 将指定的urlString下的文件下载到 savePath路径下
	 * 
	 * @param urlString
	 * @param savePath
	 * @return 保存的文件对象
	 * @throws IOException
	 */
	public File createDownLoadFile(String urlString, String savePath)
			throws IOException {
		// 1. 取出要下载的文件长度,使用 "HEAD"请求头
		long length = getDownLoadFileLength(urlString);
		// 2. 从urlString中取出文件名
		String fileName = getDownLoadFileName(urlString);
		// 3. 创建文件到moren路径或指定路径下
		File downLoadFile = createFile(savePath, fileName, length);
		return downLoadFile;
	}

	/**
	 * 取出要下载的文件的长度
	 * 
	 * @param urlString
	 *            : 要下载的文件的地址
	 * @return length: 文件长度 字节长度
	 * @throws IOException
	 */
	public long getDownLoadFileLength(String urlString) throws IOException {
		long length = -1;
		// 1.取要下载的文件 长度
		URL url = new URL(urlString);
		HttpURLConnection con = (HttpURLConnection) url.openConnection();
		con.setRequestMethod("HEAD"); // 请求头
		con.setConnectTimeout(5 * 1000); // 请求过期的时间
		con.connect();
		length = con.getContentLength();
		return length;
	}

	/**
	 * 根据url获取要下载的文件名
	 * 
	 * @param urlString
	 * @return 要下载的文件名
	 * @throws MalformedURLException
	 * @throws MalformedURLException
	 */
	public String getDownLoadFileName(String urlString)
			throws MalformedURLException {
		if (urlString == null || "".equals(urlString)) {
			throw new IllegalArgumentException("文件名不能为空");
		}
		URL url = new URL(urlString);
		String file = url.getFile();
		String fileName = file.substring(file.lastIndexOf("/") + 1);
		return fileName;
	}

	/**
	 * 根据目录名,文件名,长度,创建一个文件到指定位置
	 * 
	 * @param directory
	 *            : null "" fffff:\\
	 * @param fileName
	 * @param length
	 * @return 创建的文件对象
	 * @throws IOException
	 */
	public File createFile(String directory, String fileName, long length)
			throws IOException {
		String directoryPath = null;
		if (directory != null && !"".equals(directory)
				&& new File(directory).exists()) {
			directoryPath = directory;
		} else {
			directoryPath = System.getProperty("user.home");
		}
		if (fileName == null || "".equals(fileName)) {
			throw new IllegalArgumentException("文件名不存在");
		}
		if (length <= 0) {
			throw new IllegalArgumentException("文件大小不能小于0字节");
		}
		File f = new File(directoryPath, fileName);
		// TODO:要判断这个文件是否存在,没有则创建,有,表示有两种情况: 1. 已经 下载完成, 2. 需要断点续传..
		if (f.exists()) {
			return f;
		}
		RandomAccessFile raf = new RandomAccessFile(f, "rw");
		raf.setLength(length);
		return f;

	}
}

学习了网络线程,自己写一个程序,来加深理解








仿迅雷实现下载工具,支持断点续传和多线程下载以及简单界面展示

这篇博客介绍一下在公司实习时选做的一个项目,虽然实习期间没有全部完成,但觉得这个项目做好的话挺有意思的,就在之后几天赶着完善了这个项目。这个项目是仿照迅雷,实现一个下载工具。 (开发平台: Ubun...

Java 实现的断点下载

该断点下载可应用于浏览器或者迅雷等下载工具的下载,实现方式有多种多样的,本文只研究了单线程的下载,迅雷等下载工具会自动将下载资源分块并记录每块的起始位置,然后根据系统性能,起多线程下载。 1. 基本...

http断点续传原理:http头 Range、Content-Range

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。一般断点下载时才用到 Range 和 Content-Range 实...

Docker学习记录(二)-Dockerfile创建镜像

Docker学习记录(二)-Dockerfile创建镜像标签(空格分隔): docker 个人独立博客: http://mrdear.cn 本文学习Dcokerfile的基本命令,并且创建一个支持...

如何快速下载DockerToolbox?Boot2Docker? 使用国内开源镜像站点

如何快速下载DockerToolbox?Boot2Docker? 使用国内开源镜像站点   Docker Toolbox可以快速搭建Docker测试环境,是非常有用的工具。 但是Docker Tool...

java后台断点续传 支持html5 video及迅雷工具

最近项目需要用到视频播放,播放文件需要登录验证,及权限控制,所以写了个测试例子。 1)前端界面 您的浏览器不支持HTML5视频 图片如下: ...

断点续传下载 (JAVA版 迅雷)

  • 2015年05月05日 21:18
  • 22KB
  • 下载

Java 服务器端支持断点续传的源代码【支持快车、迅雷】

Java 服务器端支持断点续传的源代码【支持快车、迅雷】(仅支持 HTTP 协议)         网上关于 Java 支持 HTTP 断点续传的文章不少,但关于 Java 服务器端支持 HTTP...
  • defonds
  • defonds
  • 2011年12月15日 15:27
  • 12585

Java 服务器端支持断点续传的源代码【支持快车、迅雷】

Java 服务器端支持断点续传的源代码【支持 快车、迅雷】(仅支持 HTTP 协议)         网上关于 Java 支持 HTTP 断点续传的文章不少,但关于 J...
  • sbvfhp
  • sbvfhp
  • 2014年02月17日 14:40
  • 707

类似迅雷下载实现大文件断点续传

不论是网页开发还是客户端程序开发,都有可能遇到文件下载的实现,最简单的办法好像是说使用WebClient.DownLoadFile()实现,但是如果遇到大文件需要做到断点续传,怎么办?我们看看做到断点...
  • luxin10
  • luxin10
  • 2011年12月14日 17:49
  • 7503
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:自己写的一个简单的迅雷下载支持断点续传
举报原因:
原因补充:

(最多只允许输入30个字)