Java 多线程断点续传(基于Http)

概要:

所谓断点续传,是指从文件已经下载的地方开始继续下载文件,在客户端要给服务器端添加一条从哪里开始的请求。

断点续传需要服务器的支持,传统FTP服务器不支持REST指令,则FTP服务器不能支持断点续传。客户端要知道使用REST等一些指令来作断点续传。

客户端使用TYPE命令告知支持指令的FTP服务器使用BINARY模式传送文件;使用PASV命令告知FTP服务器使用被动打开模式来传送文件;使用REST指令来告知FTP服务器它需要从文件的某个点开始传,接着用STOR或RETR命令开始传文件。

| |目录

代码

package net.xsoftlab.baike;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;

class ControlFileFetch extends Thread {// 扩展线程类,负责文件的抓取,控制内部线程
	TranBean tranBean = null; // 文件信息Bean
	long[] startPosition; // 开始位置
	long[] endposition; // 结束位置
	FileFetch[] childThread; // 子线程对象
	long fileLength; // 文件长度
	boolean isFirstGet = true; // 是否第一次取文件
	boolean isStopGet = false; // 停止标志
	File fileName; // 文件下载的临时信息
	DataOutputStream output; // 输出到文件的输出流

	public ControlFileFetch(TranBean tranBean) throws IOException {
		this.tranBean = tranBean;
		fileName = new File(tranBean.getFileDir() + File.separator + tranBean.getFileName() + ".info");// 创建文件
		if (fileName.exists()) {// 判断文件是否存在
			isFirstGet = false;
			readInfo();// 调用读取保存的下载文件信息
		} else {// 文件不存在则设置开始与结束位置
			startPosition = new long[tranBean.getCount()];
			endposition = new long[tranBean.getCount()];
		}
	}

	@Override
	public void run() {// 实现线程类的方法
		try {
			if (isFirstGet) {// 第一次读取文件
				fileLength = getFileSize();// 调用方法获得文件长度
				if (fileLength == -1) {
					System.err.println("文件长度未知!");
				} else if (fileLength == -2) {
					System.err.println("不能访问文件");
				} else {
					for (int i = 0; i < startPosition.length; i++) {// 循环划分每个线程要下载的文件的开始位置
						startPosition[i] = i * (fileLength / startPosition.length);
					}
					for (int i = 0; i < endposition.length - 1; i++) {// 循环划分每个线程要下载的文件的结束位置
						endposition[i] = startPosition[i + 1];
					}
					endposition[endposition.length - 1] = fileLength;
				}
			}
			childThread = new FileFetch[startPosition.length];// 启动子线程
			for (int i = 0; i < startPosition.length; i++) {// 循环创建并启动子线程
				childThread[i] = new FileFetch(tranBean.getWebAddr(),
						tranBean.getFileDir() + File.separator + tranBean.getFileName(), startPosition[i],
						endposition[i], i);
				Log.log("线程:" + (i + 1) + ",开始位置 =" + startPosition[i] + ",结束位置=" + endposition[i]);
				childThread[i].start();// 线程启动方法
			}
			boolean breakWhile = false;
			while (!isStopGet) {// 如果没有停止一直循环下去
				savePosition();// 保存下载文件指针位置
				Log.sleep(500);
				breakWhile = true;
				for (int i = 0; i < startPosition.length; i++) {// 循环实现下载文件
					if (!childThread[i].downLoadOver) {
						breakWhile = false;
						break;
					}
				}
				if (breakWhile)
					break;
			}
			System.err.println("文件下载结束!");
		} catch (Exception e) {// 捕获异常
			System.out.println("下载文件出错:" + e.getMessage());
		}
	}

	public long getFileSize() {// 获得文件长度
		int fileLength = -1;
		try {
			URL url = new URL(tranBean.getWebAddr());// 根据传入网址创建URL对象
			HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();// 创建打开连接对象
			httpConnection.setRequestProperty("User-Agent", "NetFox");// 设置描述发出HTTP请求的终端的信息
			int responseCode = httpConnection.getResponseCode();// 获得响应代码
			if (responseCode >= 400) {// 响应代码超过指定数字不能访问文件
				errorCode(responseCode);
				return -2;
			}
			String head;
			for (int i = 1;; i++) {// 循环获得文件头部信息
				head = httpConnection.getHeaderFieldKey(i);// 获得文件头部的信息
				if (head != null) {
					if (head.equals("Content-Length")) {// 根据头部信息获得文件长度
						fileLength = Integer.parseInt(httpConnection.getHeaderField(head));
						break;
					}
				} else
					break;
			}
		} catch (Exception e) {// 捕获异常
			System.out.println("获取文件长度出错:" + e.getMessage());
		}
		Log.log("文件的长度:" + fileLength);
		return fileLength;
	}

	private void savePosition() {// 保存下载信息(文件指针位置)
		try {
			output = new DataOutputStream(new FileOutputStream(fileName));
			output.writeInt(startPosition.length);
			for (int i = 0; i < startPosition.length; i++) {
				output.writeLong(childThread[i].startPosition);// 保存每个线程的开始位置
				output.writeLong(childThread[i].endposition);// 保存每个线程的结束位置
			}
			output.close();// 释放资源
		} catch (Exception e) {// 捕获异常
			System.out.println("保存下载信息出错:" + e.getMessage());
		}
	}

	private void readInfo() {// 读取文件指针位置
		try {
			DataInputStream input = new DataInputStream(new FileInputStream(fileName));// 创建数据输入流
			int count = input.readInt();// 读取分成的线程下载个数
			startPosition = new long[count];// 设置开始线程
			endposition = new long[count];// 设置结束线程
			for (int i = 0; i < startPosition.length; i++) {
				startPosition[i] = input.readLong();// 读取每个线程的开始位置
				endposition[i] = input.readLong();// 读取每个线程的结束位置
			}
			input.close();// 释放资源
		} catch (Exception e) {// 捕获异常
			System.out.println("读取文件指针位置出现错误:" + e.getMessage());
		}
	}

	private void errorCode(int errorCode) {// 显示错误代码
		System.err.println("错误代码:" + errorCode);
	}

	public void stopDownLoad() {// 停止文件下载
		isStopGet = true;
		for (int i = 0; i < startPosition.length; i++)
			childThread[i].splitterStop();
	}
}

class FileFetch extends Thread {// 扩展线程类实现部分文件的抓取
	String webAddr; // 网址
	long startPosition; // 开始位置
	long endposition; // 结束位置
	int threadID; // 线程编号
	boolean downLoadOver = false; // 下载结束
	boolean isStopGet = false;
	FileAccess fileAccessI = null;

	public FileFetch(String sURL, String sName, long nStart, long nEnd, int id) throws IOException {// 带参数构造方法进行初始化
		this.webAddr = sURL;
		this.startPosition = nStart;
		this.endposition = nEnd;
		threadID = id;
		fileAccessI = new FileAccess(sName, startPosition);
	}

	@Override
	public void run() {// 实现线程的方法
		while (startPosition < endposition && !isStopGet) {
			try {
				URL url = new URL(webAddr);// 根据网络资源创建URL对象
				HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();// 创建打开连接的对象
				httpConnection.setRequestProperty("User-Agent", "NetFox");// 设置描述发出HTTP请求的终端的信息
				String sProperty = "bytes=" + startPosition + "-";
				httpConnection.setRequestProperty("RANGE", sProperty);// 设置描述发出HTTP请求的终端的信息
				Log.log(sProperty);
				InputStream input = httpConnection.getInputStream();// 创建输入流对象
				byte[] b = new byte[1024];
				int nRead;
				while ((nRead = input.read(b, 0, 1024)) > 0// 循环将文件下载到指定目录
						&& startPosition < endposition && !isStopGet) {
					startPosition += fileAccessI.write(b, 0, nRead);// 调用方法将内容写入文件
				}
				Log.log("线程   " + (threadID + 1) + "  结束...");
				downLoadOver = true;
			} catch (Exception e) {// 捕获异常
				e.printStackTrace();
			}
		}
	}

	public void logResponseHead(HttpURLConnection con) {// 打印回应的头信息
		for (int i = 1;; i++) {// 循环打印回应的头信息
			String header = con.getHeaderFieldKey(i);
			if (header != null)
				Log.log(header + ": " + con.getHeaderField(header));
			else
				break;
		}
	}

	public void splitterStop() {
		isStopGet = true;
	}
}

class FileAccess implements Serializable {// 存储文件的类
	RandomAccessFile saveFile;
	long position;

	public FileAccess() throws IOException {// 默认构造方法初始化
		this("", 0);
	}

	public FileAccess(String sName, long position) throws IOException {// 带参数构造方法进行初始化
		saveFile = new RandomAccessFile(sName, "rw");// 创建随机访问文件对象,以读/写方式
		this.position = position;
		saveFile.seek(position);// 设置指针位置
	}

	public synchronized int write(byte[] b, int start, int length) {
		int n = -1;
		try {
			saveFile.write(b, start, length);// 将字节数据写入文件
			n = length;
		} catch (IOException e) {// 捕获IO流异常
			e.printStackTrace();
		}
		return n;
	}
}

class TranBean {// 传输保存信息的类
	private String webAddr; // 下载地址
	private String fileDir; // 下载到指定目录
	private String fileName; // 下载后文件的新名字
	private int count; // 文件分几个线程下载,默认是3个

	public TranBean() {// 默认构造方法
		this("", "", "", 3);
	}

	public TranBean(String webAddr, String fileDir, String fileName, int count) {// 带参数的构造方法进行初始化
		this.webAddr = webAddr;
		this.fileDir = fileDir;
		this.fileName = fileName;
		this.count = count;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	public String getFileDir() {
		return fileDir;
	}

	public void setFileDir(String fileDir) {
		this.fileDir = fileDir;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getWebAddr() {
		return webAddr;
	}

	public void setWebAddr(String webAddr) {
		this.webAddr = webAddr;
	}
}

class Log {// 线程运行信息显示的日志类
	public Log() {
	}

	public static void sleep(int nSecond) {
		try {
			Thread.sleep(nSecond);
		} catch (Exception e) {
			System.out.println("线程沉睡...");
		}
	}

	public static void log(String message) {// 显示日志信息
		System.err.println(message);
	}

	public static void log(int message) {// 显示日志信息
		System.err.println(message);
	}
}

public class TestThreadsAndPointAdd {// 操作多线程支持断点续传的类
	public TestThreadsAndPointAdd(String webAddr, String fileDir, String fileName, int count) {// 构造方法进行初始化
		try {
			TranBean bean = new TranBean(webAddr, fileDir, fileName, count);
			ControlFileFetch fileFetch = new ControlFileFetch(bean);
			fileFetch.start();
		} catch (Exception e) {
			System.out.println("多线程下载文件出错:" + e.getMessage());
			System.exit(1);
		}
	}

	public static void main(String[] args) {// java程序主入口处
		String webAddress = "http://bs.baidu.com/editor/ueditor1_4_3-utf8-jsp.zip";// 下载地址
		String fileDir = "D:/";// 下载到指定的目录
		String fileName = "ueditor.rar";// 下载后文件的名字包括后缀
		int count = 5;// 文件分几个线程下载,默认是3个
		new TestThreadsAndPointAdd(webAddress, fileDir, fileName, count);// 传入参数实例化对象
	}
}


评论关闭
评论 还能输入200
评论关闭
评论 还能输入200
  • 全部评论(0)
资料加载中...
已关注 , 取消