原理:服务器cpu分配给每条线程的时间片是相同的,服务器带宽平均分配给每个线程,所以客户端开启的线程越多就能抢占到更多的服务器资源
用java实现
public class NultiDownload {
static String path = "http://192.168.43.157:8080/1.avi";
static int ThreadCount = 3;
@SuppressWarnings("resource")
public static void main(String[] args) {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (conn.getResponseCode() == 200) {
// 拿到所请求的资源文件大小
int length = conn.getContentLength();
File file = new File("1.avi");
// 生成临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
// 设置临时文件的大小
raf.setLength(length);
raf.close();
// 计算出每个线程应该下载多少字节
int size = length / ThreadCount;
for (int i = 0; i < ThreadCount; i++) {
// 计算出每个线程的开始位置和结束位置
int startIndex = i * size;
int endIndex = (i + 1) * size - 1;
if (i == ThreadCount - 1) {
endIndex = length - 1;
}
开启线程下载
new Mythread(startIndex, endIndex, i).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Mythread extends Thread {
int startIndex;
int endIndex;
int threadId;
public Mythread(int startIndex, int endIndex, int threadId) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
@Override
public void run() {
// 再次发送http请求,下载原文件
HttpURLConnection conn;
try {
URL url = new URL(NultiDownload.path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 设置本次http请求所请的数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
// 请求部分数据的响应码是206
if (conn.getResponseCode() == 206) {
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
File file = new File("1.avi");
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
// 把文件的写入位置移动到startIndex
raf.seek(startIndex);
while ((len = is.read(b)) != -1) {
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
}
raf.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
将已经下载好的保存到临时文件里去
// 生成一个专门用来记录进度的临时文件
File progreeFile = new File(threadId + ".txt");
RandomAccessFile progreeRaf = new RandomAccessFile(progreeFile, "rwd");
//每次读取流里面的数据之后,同步当前线程下载的总进度写入到临时文件中
progreeRaf.write((total+"").getBytes());
progreeRaf.close();
将临时文件读取出来,实现断点续传
File progreeFile = new File(threadId + ".txt");
//判断进度临时文件是否存在
if (progreeFile.exists()) {
FileInputStream fis = new FileInputStream(progreeFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//从进度临时文件中读取出上一次的总进度,然后与原本的开始进度位置相加,得到新的开始位置
startIndex += Integer.parseInt(br.readLine());
}
实现下载完删除文件的功能
synchronized (NultiDownload.path){
NultiDownload.finishedThread++;
if (NultiDownload.finishedThread==NultiDownload.ThreadCount) {
for(int i = 0;i<NultiDownload.ThreadCount;i++){
File file2 = new File(i+".txt");
file2.delete();
}
NultiDownload.finishedThread = 0;}}加一个线程锁,防止一个线程下载完还没结束,另一个线程进入,引起重复删除
所有代码实现如下:
public class NultiDownload {
static String path = "http://192.168.43.157:8080/1.avi";
static int ThreadCount = 3;
static int finishedThread = 0;
@SuppressWarnings("resource")
public static void main(String[] args) {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (conn.getResponseCode() == 200) {
// 拿到所请求的资源文件大小
int length = conn.getContentLength();
File file = new File("1.avi");
// 生成临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
// 设置临时文件的大小
raf.setLength(length);
raf.close();
// 计算出每个线程应该下载多少字节
int size = length / ThreadCount;
for (int i = 0; i < ThreadCount; i++) {
// 计算出每个线程的开始位置和结束位置
int startIndex = i * size;
int endIndex = (i + 1) * size - 1;
if (i == ThreadCount - 1) {
endIndex = length - 1;
}
new Mythread(startIndex, endIndex, i).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Mythread extends Thread {
int startIndex;
int endIndex;
int threadId;
public Mythread(int startIndex, int endIndex, int threadId) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;}
public void run() {
// 再次发送http请求,下载原文件
HttpURLConnection conn;
try {
File progreeFile = new File(threadId + ".txt");
//判断进度临时文件是否存在
if (progreeFile.exists()) {
FileInputStream fis = new FileInputStream(progreeFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//从进度临时文件中读取出上一次的总进度,然后与原本的开始进度位置相加,得到新的开始位置
startIndex = Integer.parseInt(br.readLine());
fis.close();
}
URL url = new URL(NultiDownload.path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 设置本次http请求所请的数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
// 请求部分数据的响应码是206
if (conn.getResponseCode() == 206) {
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
File file = new File("1.avi");
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
// 把文件的写入位置移动到startIndex
raf.seek(startIndex);
while ((len = is.read(b)) != -1) {
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
// 生成一个专门用来记录进度的临时文件
RandomAccessFile progreeRaf = new RandomAccessFile(progreeFile, "rwd");
//每次读取流里面的数据之后,同步当前线程下载的总进度写入到临时文件中
progreeRaf.write((total+"").getBytes());
progreeRaf.close();
}
raf.close();
synchronized (NultiDownload.path){
NultiDownload.finishedThread++;
if (NultiDownload.finishedThread==NultiDownload.ThreadCount) {
for(int i = 0;i<NultiDownload.ThreadCount;i++){
File file2 = new File(i+".txt");
file2.delete();
}
NultiDownload.finishedThread = 0;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}