想要下载某个国外资源,但是由于国内的防火墙,有些资源下载极慢,而且很容易失败。
我参考了 https://github.com/9crk/mget 的思路,先用他的写的 mget.sh 去下载,但还是失败(下载完成但md5比对有误,似乎数据有损坏)
别人的代码是他写的,具体实现细节我不清楚,但了解了他的思路,我就按此思路写了一个Java版的,并由此成功下载了想要的资源(开心)。完整代码如下,希望帮助得到跟我有同样困扰的人:
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
/**
* 多线程断点下载,用于解决下载国外资源慢的问题
*/
public class MutiDownload {
/** 下载缓冲区大小 **/
private static final int BUFFER_SIZE = 4096;
/** 并发线程数 **/
private static final int THREAD_NUM = 200;
/** 每个连接的允许等待时间 **/
private static final int TIMEOUT = 60000;
/** 下载地址 **/
// private static final String URL = "http://192.168.35.139:8080/MyWeb/image/Koala.jpg";
private static final String URL = "https://jaist.dl.sourceforge.net/project/pywin32/pywin32/Build%20221/pywin32-221.win-amd64-py2.7.exe";
/** 保存地址 **/
private static final String SAVE_PATH = "D://test/pywin32-221.win-amd64-py2.7.exe";
private static final Set<Integer> RUNING_LIST = new TreeSet<Integer>();
public static void main(String[] args) {
long totalLen = getLenth(URL);
System.out.println("总数据长度:" + totalLen);
// 删除旧的文件
File pathName = new File(SAVE_PATH);
if(pathName.exists()){
pathName.delete();
}
RandomAccessFile randomAccessFile;
try {
randomAccessFile = new RandomAccessFile(SAVE_PATH, "rw");
randomAccessFile.setLength(totalLen);
} catch (Exception e) {
e.printStackTrace();
return;
}
// 一个线程负责的下载量
int oneSize = (int)(totalLen / THREAD_NUM);
if(totalLen % oneSize != 0){
oneSize++;
}
System.out.println("每个线程负责下载:" + oneSize);
for (int i = 0; i < THREAD_NUM; i++) {
int start = i * oneSize;
int end = (i + 1) * oneSize;
if(end >= totalLen){
end = (int) totalLen;
}
synchronized (RUNING_LIST) {
RUNING_LIST.add(i);
}
new DownloadThread(randomAccessFile, i, URL, start, end).start();
}
}
/** 获取下载长度 **/
public static final long getLenth(String url){
long length = 0;
try {
HttpURLConnection conn = (HttpURLConnection)new URL(url).openConnection();
conn.connect();
int statusCode = conn.getResponseCode();
System.out.println("getLenth, statusCode:" + statusCode);
if(statusCode == HttpURLConnection.HTTP_OK){
length = conn.getContentLengthLong();
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return length;
}
/** 保存到文件 **/
private synchronized static void save(RandomAccessFile randomAccessFile, byte [] data, int seek, int len) throws IOException{
randomAccessFile.seek(seek);
randomAccessFile.write(data, 0, len);
}
/** 打印下载进度信息 **/
private synchronized static void printfDownloadInfo(int index){
synchronized (RUNING_LIST) {
RUNING_LIST.remove(index);
System.out.println("线程 " + index + " 下载完成, 剩余" + Arrays.toString(RUNING_LIST.toArray()));
}
}
/**
* 下载线程
*/
private static class DownloadThread extends Thread {
private RandomAccessFile randomAccessFile;
private int index;
private String url;
private int start, end;
public DownloadThread(RandomAccessFile randomAccessFile, int index, String url, int start, int end) {
super();
this.randomAccessFile = randomAccessFile;
this.index = index;
this.url = url;
this.start = start;
this.end = end;
}
@Override
public void run() {
super.run();
while (!doRun()) {
System.out.println("线程 " + index + " 下载失败,执行重试");
}
printfDownloadInfo(index);
}
private boolean doRun(){
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection)new URL(url).openConnection();
conn.setConnectTimeout(TIMEOUT);
conn.setReadTimeout(TIMEOUT);
conn.addRequestProperty("Range", "bytes=" + start +"-" + end);
conn.connect();
int statusCode = conn.getResponseCode();
System.out.println("线程" + index + ", statusCode:" + statusCode);
if(statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_PARTIAL){
int count = 0;
InputStream is = conn.getInputStream();
byte [] bs = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(bs)) > 0) {
save(randomAccessFile, bs, start + count, len);
count += len;
}
is.close();
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
conn.disconnect();
}
}
}
}