Java 网络编程(二)
我觉得仅仅只是学习还不够,必须好要学会如何运用所学的知识,所以我们综合前面学的多线程、I/O、网络的知识,写一个多线程断点续传下载的程序,下面我们来整理一下步骤:
主要使用的类有
1) java.io.InputStream
2) java.io.RandomAccessFile
3) java.net.HttpURLConnection
4) java.net.URL
1. 创建本地文件
现在的下载软件基本都是先创建本地缓存文件,然后再开始下载,这样可以防止本地磁盘不足和可以实现断点续传的功能。
2. 计算线程数及其下载位置
计算方法:
1) 开始位置:
(线程id - 1)*每一块大小
2) 结束位置:
(线程id*每一块大小) - 1
注意:文件长度不一定能够整除,最后一个线程的结束位置应该是文件的末尾
3. 开始编程
这里我就直接上代码了:
package learn.synthesize;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 下载文件的子线程 每一个线程下载对应位置的文件
*/
class DownLoadThread extends Thread {
/*线程id*/
private int threadId;
/*本线程开始下载的起始位置*/
private int startIndex;
/*本线程下载的结束位置*/
private int endIndex;
/*文件路径*/
private String filePath;
/*网络地址*/
private String netPath;
/**
* @param url
* 下载文件在服务器上的路径
* @param filePath
* 文件路径
* @param threadId
* 线程Id
* @param startIndex
* 线程下载的开始位置
* @param endIndex
* 线程下载的结束位置
*/
public DownLoadThread(String netPath, String filePath, int threadId,
int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.filePath = filePath;
this.netPath = netPath;
}
@Override
public void run() {
try {
/*根据网络地址创建URL*/
URL url = new URL(netPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
/*设置http请求头部信息RANGE,请求服务器下载部分文件 指定文件的位置*/
conn.setRequestProperty("RANGE", "bytes=" + startIndex + "-"
+ endIndex);
/*从服务器请求全部资源返回200 ok如果从服务器请求部分资源 返回 206 ok*/
int code = conn.getResponseCode();
System.out.println("线程"+threadId+"的htpp状态码:" + code);
/*已经设置了请求的位置,返回的是当前位置对应的文件的输入流*/
InputStream is = conn.getInputStream();
/*以读写方式打开文件*/
RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
/*定位文件随机写文件的位置*/
raf.seek(startIndex);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
is.close();
raf.close();
/*打印下载完毕*/
System.out.println("线程" + threadId + "下载完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MultiThreadDownloader {
/*网络路径*/
public String netPath;
/*默认线程数为3*/
public int threadCount = 3;
/*文件保存路径*/
public String filePath = "c:\\";
/**初始化参数*/
public void initDownloader(String netPath, String filePath, int tCount)
throws Exception {
this.netPath = netPath;
this.filePath = filePath;
if (tCount > 5||tCount<=0) {
threadCount = 5;
} else {
threadCount = tCount;
}
/*开始下载*/
startDownload();
}
/**开始下载*/
private void startDownload() throws Exception {
/*第1步:连接服务器,获取一个文件,获取文件的长度,在本地创建一个跟服务器一样大小的临时文件*/
URL url = new URL(netPath);
/*这里我们使用HttpURLConnection*/
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
/*服务器端返回的数据的长度,实际上就是文件的长度*/
int length = conn.getContentLength();
System.out.println("文件总长度:" + length);
/*在客户端本地创建出来一个大小跟服务器端一样大小的临时文件*/
RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
/*指定创建的这个文件的长度*/
raf.setLength(length);
raf.close();
/*平均每一个线程下载的文件大小*/
int blockSize = length / threadCount;
for (int threadId = 1; threadId <= threadCount; threadId++) {
/*第一个线程下载的开始位置*/
int startIndex = (threadId - 1) * blockSize;
int endIndex = threadId * blockSize - 1;
/*最后一个线程的下载长度不固定*/
if (threadId == threadCount) {
endIndex = length;
}
System.out.println("线程" + threadId + "下载:" + startIndex
+ "--->" + endIndex);
/*开启下载线程下载文件*/
new DownLoadThread(netPath, filePath, threadId, startIndex,
endIndex).start();
}
} else {
System.out.printf("服务器错误!");
}
}
/**测试*/
public static void main(String[] args) throws Exception {
MultiThreadDownloader downloader=new MultiThreadDownloader();
downloader.initDownloader("http://dldir1.qq.com/qqfile/qq/QQ7.2/14810/QQ7.2.exe", "E:\\下载\\QQ.exe", 6);
}
}
程序输出为:
文件总长度:57179320
线程1下载:0--->11435863
线程2下载:11435864--->22871727
线程3下载:22871728--->34307591
线程4下载:34307592--->45743455
线程5下载:45743456--->57179320
线程1的htpp状态码:206
线程3的htpp状态码:206
线程5的htpp状态码:206
线程2的htpp状态码:206
线程4的htpp状态码:206
线程5下载完毕
线程1下载完毕
线程4下载完毕
线程2下载完毕
线程3下载完毕
上面的代码注释的都比较的清楚,所以就不再过多的赘述,当然这个程序还可以写的更加的完善,还可以加入更强大的功能(比如下载线程池等等)。