您的位置:首页 > 理论基础 > 计算机网络

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

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

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协议,对于提高你的认知层次,注意不是水平,而是层次是有极大的帮助的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: