Java技術體驗,HTTP多線程下載,端口偵聽和自啓動服務

我就把幾個技術整合到了一起。包括三個部分,實現時也是逐個做到的

  1. 多線程的文件下載,HTTP協議
  2. 把這個功能做成一個HTTP的服務,偵聽在某個端口上,方便非Java的系統使用
  3. 把這個功能封裝爲一個Windows服務,在機器啓動時可以自動啓動


我們逐個看程序。

一、多線程下載

這個主要使用了HTTP協議裏面的一個Range參數,他設置了你讀取數據的其實位置和終止位置。 經常使用flashget的用戶在查看連接的詳細信息時,應該經常看到這個東西。比如

Range:bytes=100-2000

代表從100個字節的位置開始讀取,到2000個字節的位置結束,應讀取1900個字節。

程序首先拿到文件的長度,然後分配幾個線程去分別讀取各自的一段,使用了
RandomAccessFile
進行隨機位置的讀寫。

下面是完整的下載的代碼。

  1. package net.java2000.tools;
  2. import java.io.BufferedInputStream;
  3. import java.io.File;
  4. import java.io.IOException;
  5. import java.io.RandomAccessFile;
  6. import java.net.URL;
  7. import java.net.URLConnection;
  8. /**
  9.  * HTTP的多線程下載工具。
  10.  * 
  11.  * @author 趙學慶 www.java2000.net
  12.  */
  13. public class HTTPDownloader extends Thread {
  14.   // 要下載的頁面
  15.   private String page;
  16.   // 保存的路徑
  17.   private String savePath;
  18.   // 線程數
  19.   private int threadNumber = 2;
  20.   // 來源地址
  21.   private String referer;
  22.   // 最小的塊尺寸。如果文件尺寸除以線程數小於這個,則會減少線程數。
  23.   private int MIN_BLOCK = 10 * 1024;
  24.   public static void main(String[] args) throws Exception {
  25.     HTTPDownloader d = new HTTPDownloader("http://www.xxxx.net/xxxx.rar""d://xxxx.rar"10);
  26.     d.down();
  27.   }
  28.   public void run() {
  29.     try {
  30.       down();
  31.     } catch (Exception e) {
  32.       e.printStackTrace();
  33.     }
  34.   }
  35.   /**
  36.    * 下載操作
  37.    * 
  38.    * @throws Exception
  39.    */
  40.   public void down() throws Exception {
  41.     URL url = new URL(page); // 創建URL
  42.     URLConnection con = url.openConnection(); // 建立連接
  43.     int contentLen = con.getContentLength(); // 獲得資源長度
  44.     if (contentLen / MIN_BLOCK + 1 < threadNumber) {
  45.       threadNumber = contentLen / MIN_BLOCK + 1// 調整下載線程數
  46.     }
  47.     if (threadNumber > 10) {
  48.       threadNumber = 10;
  49.     }
  50.     int begin = 0;
  51.     int step = contentLen / threadNumber;
  52.     int end = 0;
  53.     for (int i = 0; i < threadNumber; i++) {
  54.       end += step;
  55.       if (end > contentLen) {
  56.         end = contentLen;
  57.       }
  58.       new HTTPDownloaderThread(this, i, begin, end).start();
  59.       begin = end;
  60.     }
  61.   }
  62.   public HTTPDownloader() {
  63.   }
  64.   /**
  65.    * 下載
  66.    * 
  67.    * @param page 被下載的頁面
  68.    * @param savePath 保存的路徑
  69.    */
  70.   public HTTPDownloader(String page, String savePath) {
  71.     this(page, savePath, 10);
  72.   }
  73.   /**
  74.    * 下載
  75.    * 
  76.    * @param page 被下載的頁面
  77.    * @param savePath 保存的路徑
  78.    * @param threadNumber 線程數
  79.    */
  80.   public HTTPDownloader(String page, String savePath, int threadNumber) {
  81.     this(page, page, savePath, 10);
  82.   }
  83.   /**
  84.    * 下載
  85.    * 
  86.    * @param page 被下載的頁面
  87.    * @param savePath 保存的路徑
  88.    * @param threadNumber 線程數
  89.    * @param referer 來源
  90.    */
  91.   public HTTPDownloader(String page, String referer, String savePath, int threadNumber) {
  92.     this.page = page;
  93.     this.savePath = savePath;
  94.     this.threadNumber = threadNumber;
  95.     this.referer = referer;
  96.   }
  97.   public String getPage() {
  98.     return page;
  99.   }
  100.   public void setPage(String page) {
  101.     this.page = page;
  102.   }
  103.   public String getSavePath() {
  104.     return savePath;
  105.   }
  106.   public void setSavePath(String savePath) {
  107.     this.savePath = savePath;
  108.   }
  109.   public int getThreadNumber() {
  110.     return threadNumber;
  111.   }
  112.   public void setThreadNumber(int threadNumber) {
  113.     this.threadNumber = threadNumber;
  114.   }
  115.   public String getReferer() {
  116.     return referer;
  117.   }
  118.   public void setReferer(String referer) {
  119.     this.referer = referer;
  120.   }
  121. }
  122. /**
  123.  * 下載線程
  124.  * 
  125.  * @author 趙學慶 www.java2000.net
  126.  */
  127. class HTTPDownloaderThread extends Thread {
  128.   HTTPDownloader manager;
  129.   int startPos;
  130.   int endPos;
  131.   int id;
  132.   int curPos;
  133.   int BUFFER_SIZE = 4096;
  134.   int readByte = 0;
  135.   HTTPDownloaderThread(HTTPDownloader manager, int id, int startPos, int endPos) {
  136.     this.id = id;
  137.     this.manager = manager;
  138.     this.startPos = startPos;
  139.     this.endPos = endPos;
  140.   }
  141.   public void run() {
  142.     // System.out.println("線程" + id + "啓動");
  143.     // 創建一個buff
  144.     BufferedInputStream bis = null;
  145.     RandomAccessFile fos = null;
  146.     // 緩衝區大小
  147.     byte[] buf = new byte[BUFFER_SIZE];
  148.     URLConnection con = null;
  149.     try {
  150.       File file = new File(manager.getSavePath());
  151.       // 創建RandomAccessFile
  152.       fos = new RandomAccessFile(file, "rw");
  153.       // 從startPos開始
  154.       fos.seek(startPos);
  155.       // 創建連接,這裏會爲每個線程都創建一個連接
  156.       URL url = new URL(manager.getPage());
  157.       con = url.openConnection();
  158.       con.setAllowUserInteraction(true);
  159.       curPos = startPos;
  160.       // 設置獲取資源數據的範圍,從startPos到endPos
  161.       con.setRequestProperty("Range""bytes=" + startPos + "-" + endPos);
  162.       // 盜鏈解決
  163.       con.setRequestProperty("referer", manager.getReferer() == null ? manager.getPage() : manager.getReferer());
  164.       con.setRequestProperty("userAgent""Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
  165.       // 下面一段向根據文件寫入數據,curPos爲當前寫入的未知,這裏會判斷是否小於endPos,
  166.       // 如果超過endPos就代表該線程已經執行完畢
  167.       bis = new BufferedInputStream(con.getInputStream());
  168.       while (curPos < endPos) {
  169.         int len = bis.read(buf, 0, BUFFER_SIZE);
  170.         if (len == -1) {
  171.           break;
  172.         }
  173.         fos.write(buf, 0, len);
  174.         curPos = curPos + len;
  175.         if (curPos > endPos) {
  176.           // 獲取正確讀取的字節數
  177.           readByte += len - (curPos - endPos) + 1;
  178.         } else {
  179.           readByte += len;
  180.         }
  181.       }
  182.       // System.out.println("線程" + id + "已經下載完畢:" + readByte);
  183.       bis.close();
  184.       fos.close();
  185.     } catch (IOException ex) {
  186.       ex.printStackTrace();
  187.     }
  188.   }
  189. }



二、做成Http的服務,偵聽某個端口
使用了JDK6的特性,大家自己看代碼吧,並不複雜

  1. package net.java2000.tools;
  2. import java.io.IOException;
  3. import java.io.OutputStream;
  4. import java.net.InetSocketAddress;
  5. import java.net.URLDecoder;
  6. import java.util.regex.Matcher;
  7. import java.util.regex.Pattern;
  8. import com.sun.net.httpserver.HttpExchange;
  9. import com.sun.net.httpserver.HttpHandler;
  10. import com.sun.net.httpserver.HttpServer;
  11. import com.sun.net.httpserver.spi.HttpServerProvider;
  12. /**
  13.  * 下載程序的服務版本。<br>
  14.  * 可以供其它程序調用,而不是侷限於java
  15.  * 
  16.  * @author 趙學慶 www.java2000.net
  17.  */
  18. public class HTTPDownloadService {
  19.   /**
  20.    * @param args
  21.    * @throws IOException
  22.    */
  23.   public static void main(String[] args) throws IOException {
  24.     HttpServerProvider httpServerProvider = HttpServerProvider.provider();
  25.     int port = 60080;
  26.     if (args.length > 0) {
  27.       try {
  28.         port = Integer.parseInt(args[0]);
  29.       } catch (Exception ex) {}
  30.     }
  31.     // 綁定端口
  32.     InetSocketAddress addr = new InetSocketAddress(port);
  33.     HttpServer httpServer = httpServerProvider.createHttpServer(addr, 1);
  34.     httpServer.createContext("/"new HTTPDownloaderServiceHandler());
  35.     httpServer.setExecutor(null);
  36.     httpServer.start();
  37.     System.out.println("started");
  38.   }
  39. }
  40. /**
  41.  * 下載的服務器
  42.  * 
  43.  * @author 趙學慶 www.java2000.net
  44.  */
  45. class HTTPDownloaderServiceHandler implements HttpHandler {
  46.   private static Pattern p = Pattern.compile("//?page=(.*?)//&rpage=(.*?)&savepath=(.*)$");
  47.   public void handle(HttpExchange httpExchange) {
  48.     try {
  49.       Matcher m = p.matcher(httpExchange.getRequestURI().toString());
  50.       String response = "OK";
  51.       if (m.find()) {
  52.         try {
  53.           new HTTPDownloader(URLDecoder.decode(m.group(1), "GBK"), URLDecoder.decode(m.group(2), "GBK"), URLDecoder.decode(m.group(3), "GBK"), 10).start();
  54.         } catch (Exception e) {
  55.           response = "ERROR -1";
  56.           e.printStackTrace();
  57.         }
  58.       } else {
  59.         response = "ERROR 1";
  60.       }
  61.       httpExchange.sendResponseHeaders(200, response.getBytes().length);
  62.       OutputStream out = httpExchange.getResponseBody();
  63.       out.write(response.getBytes());
  64.       out.close();
  65.       httpExchange.close();
  66.     } catch (Exception ex) {
  67.       ex.printStackTrace();
  68.     }
  69.   }
  70. }


這個程序可以單獨運行的,通過
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包供下載學習和研究用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章