程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java技術體驗:HTTP多線程下載,端口偵聽和自啟動服務

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

編輯:關於JAVA

一個網友正好需要這個東西,我就把幾個技術整合到了一起。包括三個部分,實現時也是逐個做到的

多線程的文件下載,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.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協議,對於提高你的認知層次,注意不是水平,而是層次是有極大的幫助的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved