我就把幾個技術整合到了一起。包括三個部分,實現時也是逐個做到的
- 多線程的文件下載,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包供下載學習和研究用。