一个网友正好需要这个东西,我就把几个技术整合到了一起。包括三个部分,实现时也是逐个做到的
- 多线程的文件下载,HTTP协议
- 把这个功能做成一个HTTP的服务,侦听在某个端口上,方便非Java的系统使用
- 把这个功能封装为一个Windows服务,在机器启动时可以自动启动
我们逐个看程序。
一、多线程下载
这个主要使用了HTTP协议里面的一个Range参数,他设置了你读取数据的其实位置和终止位置。 经常使用flashget的用户在查看连接的详细信息时,应该经常看到这个东西。比如
Range:bytes=100-2000
代表从100个字节的位置开始读取,到2000个字节的位置结束,应读取1900个字节。
程序首先拿到文件的长度,然后分配几个线程去分别读取各自的一段,使用了
RandomAccessFile
进行随机位置的读写。
下面是完整的下载的代码。
- package net.java2000.tools;
- import java.io.BufferedInputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.RandomAccessFile;
- import java.net.URL;
- import java.net.URLConnection;
- /**
- * HTTP的多线程下载工具。
- *
- * @author 赵学庆 www.java2000.net
- */
- public class HTTPDownloader extends Thread {
- // 要下载的页面
- private String page;
- // 保存的路径
- private String savePath;
- // 线程数
- private int threadNumber = 2;
- // 来源地址
- private String referer;
- // 最小的块尺寸。如果文件尺寸除以线程数小于这个,则会减少线程数。
- private int MIN_BLOCK = 10 * 1024;
- public static void main(String[] args) throws Exception {
- HTTPDownloader d = new HTTPDownloader("http://www.xxxx.net/xxxx.rar", "d://xxxx.rar", 10);
- d.down();
- }
- public void run() {
- try {
- down();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 下载操作
- *
- * @throws Exception
- */
- public void down() throws Exception {
- URL url = new URL(page); // 创建URL
- URLConnection con = url.openConnection(); // 建立连接
- int contentLen = con.getContentLength(); // 获得资源长度
- if (contentLen / MIN_BLOCK + 1 < threadNumber) {
- threadNumber = contentLen / MIN_BLOCK + 1; // 调整下载线程数
- }
- if (threadNumber > 10) {
- threadNumber = 10;
- }
- int begin = 0;
- int step = contentLen / threadNumber;
- int end = 0;
- for (int i = 0; i < threadNumber; i++) {
- end += step;
- if (end > contentLen) {
- end = contentLen;
- }
- new HTTPDownloaderThread(this, i, begin, end).start();
- begin = end;
- }
- }
- public HTTPDownloader() {
- }
- /**
- * 下载
- *
- * @param page 被下载的页面
- * @param savePath 保存的路径
- */
- public HTTPDownloader(String page, String savePath) {
- this(page, savePath, 10);
- }
- /**
- * 下载
- *
- * @param page 被下载的页面
- * @param savePath 保存的路径
- * @param threadNumber 线程数
- */
- public HTTPDownloader(String page, String savePath, int threadNumber) {
- this(page, page, savePath, 10);
- }
- /**
- * 下载
- *
- * @param page 被下载的页面
- * @param savePath 保存的路径
- * @param threadNumber 线程数
- * @param referer 来源
- */
- public HTTPDownloader(String page, String referer, String savePath, int threadNumber) {
- this.page = page;
- this.savePath = savePath;
- this.threadNumber = threadNumber;
- this.referer = referer;
- }
- public String getPage() {
- return page;
- }
- public void setPage(String page) {
- this.page = page;
- }
- public String getSavePath() {
- return savePath;
- }
- public void setSavePath(String savePath) {
- this.savePath = savePath;
- }
- public int getThreadNumber() {
- return threadNumber;
- }
- public void setThreadNumber(int threadNumber) {
- this.threadNumber = threadNumber;
- }
- public String getReferer() {
- return referer;
- }
- public void setReferer(String referer) {
- this.referer = referer;
- }
- }
- /**
- * 下载线程
- *
- * @author 赵学庆 www.java2000.net
- */
- class HTTPDownloaderThread extends Thread {
- HTTPDownloader manager;
- int startPos;
- int endPos;
- int id;
- int curPos;
- int BUFFER_SIZE = 4096;
- int readByte = 0;
- HTTPDownloaderThread(HTTPDownloader manager, int id, int startPos, int endPos) {
- this.id = id;
- this.manager = manager;
- this.startPos = startPos;
- this.endPos = endPos;
- }
- public void run() {
- // System.out.println("线程" + id + "启动");
- // 创建一个buff
- BufferedInputStream bis = null;
- RandomAccessFile fos = null;
- // 缓冲区大小
- byte[] buf = new byte[BUFFER_SIZE];
- URLConnection con = null;
- try {
- File file = new File(manager.getSavePath());
- // 创建RandomAccessFile
- fos = new RandomAccessFile(file, "rw");
- // 从startPos开始
- fos.seek(startPos);
- // 创建连接,这里会为每个线程都创建一个连接
- URL url = new URL(manager.getPage());
- con = url.openConnection();
- con.setAllowUserInteraction(true);
- curPos = startPos;
- // 设置获取资源数据的范围,从startPos到endPos
- con.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
- // 盗链解决
- con.setRequestProperty("referer", manager.getReferer() == null ? manager.getPage() : manager.getReferer());
- con.setRequestProperty("userAgent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
- // 下面一段向根据文件写入数据,curPos为当前写入的未知,这里会判断是否小于endPos,
- // 如果超过endPos就代表该线程已经执行完毕
- bis = new BufferedInputStream(con.getInputStream());
- while (curPos < endPos) {
- int len = bis.read(buf, 0, BUFFER_SIZE);
- if (len == -1) {
- break;
- }
- fos.write(buf, 0, len);
- curPos = curPos + len;
- if (curPos > endPos) {
- // 获取正确读取的字节数
- readByte += len - (curPos - endPos) + 1;
- } else {
- readByte += len;
- }
- }
- // System.out.println("线程" + id + "已经下载完毕:" + readByte);
- bis.close();
- fos.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
二、做成Http的服务,侦听某个端口
使用了JDK6的特性,大家自己看代码吧,并不复杂
- package net.java2000.tools;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.net.InetSocketAddress;
- import java.net.URLDecoder;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import com.sun.net.httpserver.HttpExchange;
- import com.sun.net.httpserver.HttpHandler;
- import com.sun.net.httpserver.HttpServer;
- import com.sun.net.httpserver.spi.HttpServerProvider;
- /**
- * 下载程序的服务版本。<br>
- * 可以供其它程序调用,而不是局限于java
- *
- * @author 赵学庆 www.java2000.net
- */
- public class HTTPDownloadService {
- /**
- * @param args
- * @throws IOException
- */
- public static void main(String[] args) throws IOException {
- HttpServerProvider httpServerProvider = HttpServerProvider.provider();
- int port = 60080;
- if (args.length > 0) {
- try {
- port = Integer.parseInt(args[0]);
- } catch (Exception ex) {}
- }
- // 绑定端口
- InetSocketAddress addr = new InetSocketAddress(port);
- HttpServer httpServer = httpServerProvider.createHttpServer(addr, 1);
- httpServer.createContext("/", new HTTPDownloaderServiceHandler());
- httpServer.setExecutor(null);
- httpServer.start();
- System.out.println("started");
- }
- }
- /**
- * 下载的服务器
- *
- * @author 赵学庆 www.java2000.net
- */
- class HTTPDownloaderServiceHandler implements HttpHandler {
- private static Pattern p = Pattern.compile("//?page=(.*?)//&rpage=(.*?)&savepath=(.*)$");
- public void handle(HttpExchange httpExchange) {
- try {
- Matcher m = p.matcher(httpExchange.getRequestURI().toString());
- String response = "OK";
- if (m.find()) {
- try {
- new HTTPDownloader(URLDecoder.decode(m.group(1), "GBK"), URLDecoder.decode(m.group(2), "GBK"), URLDecoder.decode(m.group(3), "GBK"), 10).start();
- } catch (Exception e) {
- response = "ERROR -1";
- e.printStackTrace();
- }
- } else {
- response = "ERROR 1";
- }
- httpExchange.sendResponseHeaders(200, response.getBytes().length);
- OutputStream out = httpExchange.getResponseBody();
- out.write(response.getBytes());
- out.close();
- httpExchange.close();
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
这个程序可以单独运行的,通过
http://127.0.0.1:60080
访问,参数是固定顺序的,比如
http://127.0.0.1:60080?page=http://www.youkuaiyun.com&rpage=http://blog.youkuaiyun.com&savepath=d:/csdn.html
三个参数分别代表了
page = 被下载的页面
rpage = referer的页面,用来对付防盗链的系统
savepath = 下载后保存的文件
三、改造成Windows的服务
主要使用了这个技术,不是很复杂 http://www.java2000.net/p598
所需要的东西我已经全部提供,且提供了一个完整的zip包供下载学习和研究用。
下载地址和原文请看这里: http://www.java2000.net/p9398
总结:
希望对web变成有兴趣的,应该多了解一下HTTP协议,对于提高你的认知层次,注意不是水平,而是层次是有极大的帮助的。
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>