一個網友正好需要這個東西,我就把幾個技術整合到了一起。包括三個部分,實現時也是逐個做到的
多線程的文件下載,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協議,對於提高你的認知層次,注意不是水平,而是層次是有極大的幫助的。