首先说明:本文Java代码最初来源于网上,本人只是在原作的基础上修改。
概要:本例用于下载大容量的附件,可以启用多线程分段下载,掉线/请求暂停后可以继续下载。原理很简单,其实就是在本地目录下增加一个 *.info文件,以记录当前的位置;再次下载时读取这些信息,同时设置HttpURLConnection的头信息“User-Agent=NetFox; RANGE=bytes=xxx-”。
文档结构:
DownloadTask.java-主线程,启动Listener和Worker,下载完成删除info文件
DownloadListener.java-监听下载进度,当需要时写入info文件
DownloadWorker.java-下载一段内容
DownloadItem.java-下载对象
Main.java-测试程序。
------------------------------------
public class DownloadItem {
private String siteURL; // Site's URL
private String filePath; // Saved File's Path
public DownloadItem(String webUrl, String localPath) {
this.setSiteURL(webUrl);
this.setFilePath(localPath);
}
public String getSiteURL() {
return siteURL;
}
public void setSiteURL(String value) {
siteURL = value;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String value) {
filePath = value;
}
}
-----------------------------------
public class DownloadWorker extends Thread {
private String sURL; // File URL
private long startPos; // File Snippet Start Position
private long endPos; // File Snippet End Position
private FileAccess fileAccess; // File Access interface
private int size = 10240; // data size
boolean ifStop = false; //客户端请求暂停或断线/IO等异常
boolean isDownOver = false; // download is successful
private static Logger logger = Logger.getLogger("SiteFileFetch");
public DownloadWorker(String sURL, String sName, long nStart, long nEnd,
int id) throws IOException {
super(String.valueOf(id));
this.sURL = sURL;
this.startPos = nStart;
this.endPos = nEnd;
fileAccess = new FileAccess(sName, startPos);
}
public void run(){
if (startPos >= endPos){
isDownOver = true;
return;
}
try {
HttpURLConnection httpConnection = (HttpURLConnection)
new URL(sURL).openConnection();
httpConnection.setRequestProperty("User-Agent", "NetFox");
String sProperty = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", sProperty);
InputStream input = httpConnection.getInputStream();
byte[] b = new byte[size];
int nRead;
while (startPos < endPos && !ifStop
&& (nRead = input.read(b, 0, (int)Math.min(endPos-startPos, size))) > 0) {
startPos += fileAccess.write(b, 0, nRead);
}
isDownOver = startPos >= endPos || !ifStop;
if (isDownOver){
System.out.println( "Thread " + this.getName() + " is over!");
}else if (ifStop){
System.out.println( "Thread " + this.getName() + " is paused!");
}
} catch (java.io.IOException e) {
ifStop = true;
throw new RuntimeException(e);
}
}
// 打印回应的头信息
public void logResponseHead(HttpURLConnection con) {
for (int i=1; ; i++) {
String header = con.getHeaderFieldKey(i);
if (header != null){
logger.log(Level.FINE, header + " : " + con.getHeaderField(header));
}else{
break;
}
}
}
public void requestStop() {
this.ifStop = true;
}
public long getStartPos() {
return startPos;
}
public long getEndPos() {
return endPos;
}
public void setSize(int size){
this.size = size;
}
}
--------------------------------------
public class DownloadTask extends Thread {
private DownloadItem downloadItem; // 文件信息Bean
private int ants; // 线程数
private long[] startPos; // 开始位置
private long[] endPos; // 结束位置
private DownloadWorker[] workers; // 子线程对象
private boolean stopped = false; // 停止标志
private File tmpFile; // 文件下载的临时信息
private long startTimeStamp = 0;
public DownloadTask(DownloadItem bean, int ants) throws IOException {
downloadItem = bean;
this.ants = ants;
tmpFile = new File(bean.getFilePath() + ".info");
}
public void run() {
try {
if (!tmpFile.exists()) {
if (this.splitFile()){
System.err.println("文件下载结束!");
return;
}
}else{
this.read_nPos();
}
startTimeStamp = System.currentTimeMillis();
// 启动子线程
workers = new DownloadWorker[startPos.length];
for (int i = 0; i < workers.length; i++){
workers[i] = new DownloadWorker(downloadItem.getSiteURL(),
downloadItem.getFilePath(), startPos[i], endPos[i], i);
System.out.println("[Thread " + i + "], startPos = " + startPos[i]
+ ", endPos = " + endPos[i]);
workers[i].start();
}
new DownloadListener().start();
// 等待子线程返回
while (!stopped) {
sleep(500);
if (this.isDownOver()){
stopped = true;
System.err.println("文件下载结束!");
System.err.println(System.currentTimeMillis()-startTimeStamp);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 停止文件下载
public void pauseTask(){
stopped = true;
for (int i = 0; i < startPos.length; i++){
workers[i].requestStop();
}
}
// 获得文件长度
public long getFileSize() {
int nFileLength = -1;
try {
URL url = new URL(downloadItem.getSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestProperty("User-Agent", "NetFox");
int responseCode = httpConnection.getResponseCode();
if (responseCode >= 400){
processErrorCode(responseCode);
return -2; // can't access file
}
nFileLength = Integer.parseInt(
httpConnection.getHeaderField("Content-Length") );
} catch (IOException e) {
e.printStackTrace();
}
return nFileLength;
}
// 获得当前完成的字节数
public long getCurFinishedSize(){
long sum = 0;
for (DownloadWorker ant : workers){
sum += (ant.getEndPos() - ant.getStartPos());
}
return sum;
}
// 将文件拆分N个线程,每个线程从不同的位置开始下载
private boolean splitFile(){
long fileLen = getFileSize(); // 获得Web站点文件长度
File localFile = new File(downloadItem.getFilePath());
if (localFile.exists()){
if (localFile.getTotalSpace() >= fileLen){ //已经下载完成
return true;
}else{ //丢失了Info文件,重新下载
localFile.delete();
}
}
startPos = new long[ants];
endPos = new long[ants];
if (fileLen == -1) {
System.err.println("File Length is not known!");
} else if (fileLen == -2) {
System.err.println("File is not access!");
} else {
long avg = fileLen / ants;
for (int i = 0; i < ants; i++) {
startPos[i] = (long) (i * avg);
}
for (int i = 0; i < ants-1; i++) {
endPos[i] = startPos[i + 1];
}
endPos[ants-1] = fileLen;
}
return false;
}
// 读取保存的下载信息(文件指针位置)
private void read_nPos() {
DataInputStream input = null;
try {
input = new DataInputStream(new FileInputStream(tmpFile));
int nCount = input.readInt();
startPos = new long[nCount];
endPos = new long[nCount];
for (int i = 0; i < startPos.length; i++) {
startPos[i] = input.readLong();
endPos[i] = input.readLong();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if (input != null){
try {
input.close();
} catch (IOException e) {
}
input = null;
}
}
}
private void processErrorCode(int nErrorCode) {
System.err.println("Error Code : " + nErrorCode);
}
//文件是否下载完成
private boolean isDownOver(){
int overs = 0;
for (DownloadWorker ant : workers){
if (ant.isDownOver){
overs ++;
}
}
return overs == ants;
}
class DownloadListener extends Thread{
private DataOutputStream output; // 输出到文件的输出流
public void run(){
while (!stopped){
boolean required = false;
for (DownloadWorker ant : workers){
if (ant.ifStop){
required = true;
break;
}
}
if (required){
this.write_nPos();
}
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.write_nPos();
if (isDownOver()){
tmpFile.delete();
}
}
// 保存下载信息(文件指针位置)
private void write_nPos() {
try {
output = new DataOutputStream(new FileOutputStream(tmpFile));
output.writeInt(startPos.length);
for (DownloadWorker ant : workers){
output.writeLong(ant.getStartPos());
output.writeLong(ant.getEndPos());
}
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
--------------------------------------------
public class Main {
public static void main(String[] args){
DownloadItem bean = new DownloadItem(
"http://localhost:88/cxf/server103_win32.exe", "E:/temp/server103_win32.exe");
try {
DownloadTask task = new DownloadTask(bean, 5);
task.start();
// Thread.sleep(10000);
// task.pauseTask();
} catch (Exception e) {
e.printStackTrace();
}
}
}