现在的位置: 首页 > 综合 > 正文

Java技术体验,HTTP多线程下载,端口侦听和自启动服务

2013年05月28日 ⁄ 综合 ⁄ 共 7080字 ⁄ 字号 评论关闭

 

一个网友正好需要这个东西,我就把几个技术整合到了一起。包括三个部分,实现时也是逐个做到的

1、多线程的文件下载,HTTP协议 
2、把这个功能做成一个HTTP的服务,侦听在某个端口上,方便非Java的系统使用 
3、把这个功能封装为一个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.csdn.net&rpage=http://blog.csdn.net&savepath=d:/csdn.html

三个参数分别代表了
page = 被下载的页面
rpage = referer的页面,用来对付防盗链的系统
savepath = 下载后保存的文件

 

三、改造成Windows的服务

主要使用了这个技术,不是很复杂 http://www.java2000.net/p598

所需要的东西我已经全部提供,且提供了一个完整的zip包供下载学习和研究用。

下载地址和原文请看这里: http://www.java2000.net/p9398

 

总结:
  希望对web变成有兴趣的,应该多了解一下HTTP协议,对于提高你的认知层次,注意不是水平,而是层次是有极大的帮助的

 

抱歉!评论已关闭.