Http多线程下载与断点续传分析
找了很多天的工作,真是找得有点郁闷,也就惰了下来!发现现在的简历真是不值钱。上次面试的时候本来投的是“高级程序员”职位,笔试也笔试,面试也面了。本来还是信心满满的. 不过后来在谈到薪水的时候,被贬到了初级程序员,给的高级程序员标准我还达不到,中级程序员的标准也需要一定条件--中级证书,郁闷的是我也没有!最后被定位为初级程序员!还真是有点打击人。
不过我到现在也不知道初中高级程序员的标准究竟在哪里,平时总是尽量让自己提高,也很少去考滤到证书的重要性!总之最后就是泡汤了
好了,口水就吐到这里吧,转入正题!投简历归投简历,剩余时间总是喜欢做一点自己喜欢做的事。
有时候发现最快乐的事就是静静的听着音乐,敲着代码,写一些东西与朋友一起分享,一起研究。
上次的 - “Mp3在线搜索工具”还有很多可以改进的地方,也得到一些朋友的建议,非常感谢。这个版本中加入了断点续传的功能,使用了XML文件保存任务列表及状态信息,并且支持多线程分段下载, 提高下载速度,在这一个版本中,我把它叫做: JLoading 因为我还想不出一个更好听一点或更酷一些的名字, 而且我还想让他可以下载一些其它文件。程序不想做大,但想做得极致一些,比如从速度上。欢迎交流学习, huliqing(huliqing@live.com)
Jloading完整程序下载
Jloading源码下载(仅供学习研究使用,有任何问题请与本人联系)
协议针对于Http,先谈一下简单原理。因为代码太多,在这里只取重点分析。
如果你对http协议比较了解,那么你应该已经知道原理了,只要在请求头中加入以下代码就可以只请求部分数据: Content-Range: bytes 20000-40000/47000 ,
即从第20000字节请求到第40000个字节,(文件长度是47000字节).知道了这一点之后,请求数据就非常容易了,
只要利用Java中的URL,先探测数据的长度,然后再将这个长度分片段进行多段程下载就可以了,以下是我的实现方式:
程序根据线程数目划分成相应的分片信息(Piece)并放入任务列表中,由线程池中的线程去负责下载,每个线程负责一个片段(Piece),当线程下载完自己的分片之后并不立即消毁,而是到任务队列中继续获取任务,
若任务池为空,则将自己放入空闲池中并等待新任务,其他线程在发现有空闲线程时,则将自己所负责的任务分片再进行切割,并放入到任务队列中,同时唤醒空闲线程帮助下载,这样不会出现懒惰线程,也可以实现动态增删线程的功能,注意的是一些线程同步的问题。
这里使用了RandomAccessFile进行保存本地文件,使用RandomAccessFile可以快速移动指针。另外一个就是需要在运行过程中,记得定时保存分片信息,以免在程序意外崩溃的情况下无法正常保存状态信息。
程序可能还存在一些不足并且还有很多可以改进的地方。
以下是状态文件Config的XML结构,这里记录了每个任务的运行状态,piece作为每个分段的状态,在程序重启之后载入这个配置文件,并在下载完成之后删除这一文件.另外主目录还有一个task.xml文件,用于记录所有下载任务,及状态文件的位置。
- huliqing@huliqing.name
找了很多天的工作,真是找得有点郁闷,也就惰了下来!发现现在的简历真是不值钱。上次面试的时候本来投的是“高级程序员”职位,笔试也笔试,面试也面了。本来还是信心满满的. 不过后来在谈到薪水的时候,被贬到了初级程序员,给的高级程序员标准我还达不到,中级程序员的标准也需要一定条件--中级证书,郁闷的是我也没有!最后被定位为初级程序员!还真是有点打击人。
不过我到现在也不知道初中高级程序员的标准究竟在哪里,平时总是尽量让自己提高,也很少去考滤到证书的重要性!总之最后就是泡汤了
好了,口水就吐到这里吧,转入正题!投简历归投简历,剩余时间总是喜欢做一点自己喜欢做的事。
有时候发现最快乐的事就是静静的听着音乐,敲着代码,写一些东西与朋友一起分享,一起研究。
上次的 - “Mp3在线搜索工具”还有很多可以改进的地方,也得到一些朋友的建议,非常感谢。这个版本中加入了断点续传的功能,使用了XML文件保存任务列表及状态信息,并且支持多线程分段下载, 提高下载速度,在这一个版本中,我把它叫做: JLoading 因为我还想不出一个更好听一点或更酷一些的名字, 而且我还想让他可以下载一些其它文件。程序不想做大,但想做得极致一些,比如从速度上。欢迎交流学习, huliqing(huliqing@live.com)
Jloading完整程序下载
Jloading源码下载(仅供学习研究使用,有任何问题请与本人联系)
协议针对于Http,先谈一下简单原理。因为代码太多,在这里只取重点分析。
如果你对http协议比较了解,那么你应该已经知道原理了,只要在请求头中加入以下代码就可以只请求部分数据: Content-Range: bytes 20000-40000/47000 ,
即从第20000字节请求到第40000个字节,(文件长度是47000字节).知道了这一点之后,请求数据就非常容易了,
只要利用Java中的URL,先探测数据的长度,然后再将这个长度分片段进行多段程下载就可以了,以下是我的实现方式:
//
对文件进行分块
try {
totalBytes = new URL(url).openConnection().getContentLength();
if (totalBytes == - 1 ) state = STATE_NONE;
} catch (IOException ioe) {
return ;
}
// 创建分块,并创建相应的负责下载的线程
long pieceSize = ( long ) Math.ceil(( double ) totalBytes / ( double ) threads);
long pStart = 0 ;
long pEnd = 0 ;
tasksAll.clear();
tasks.clear();
for ( int i = 0 ; i < threads; i ++ ) {
if (i == 0 ) {
pStart = pieceSize * i;
}
if (i == threads - 1 ) {
pEnd = totalBytes;
} else {
pEnd = pStart + pieceSize;
}
Piece piece = new Piece(pStart, pStart, pEnd);
tasksAll.add(piece);
tasks.add(piece);
pStart = pEnd + 1 ;
}
try {
totalBytes = new URL(url).openConnection().getContentLength();
if (totalBytes == - 1 ) state = STATE_NONE;
} catch (IOException ioe) {
return ;
}
// 创建分块,并创建相应的负责下载的线程
long pieceSize = ( long ) Math.ceil(( double ) totalBytes / ( double ) threads);
long pStart = 0 ;
long pEnd = 0 ;
tasksAll.clear();
tasks.clear();
for ( int i = 0 ; i < threads; i ++ ) {
if (i == 0 ) {
pStart = pieceSize * i;
}
if (i == threads - 1 ) {
pEnd = totalBytes;
} else {
pEnd = pStart + pieceSize;
}
Piece piece = new Piece(pStart, pStart, pEnd);
tasksAll.add(piece);
tasks.add(piece);
pStart = pEnd + 1 ;
}
程序根据线程数目划分成相应的分片信息(Piece)并放入任务列表中,由线程池中的线程去负责下载,每个线程负责一个片段(Piece),当线程下载完自己的分片之后并不立即消毁,而是到任务队列中继续获取任务,
若任务池为空,则将自己放入空闲池中并等待新任务,其他线程在发现有空闲线程时,则将自己所负责的任务分片再进行切割,并放入到任务队列中,同时唤醒空闲线程帮助下载,这样不会出现懒惰线程,也可以实现动态增删线程的功能,注意的是一些线程同步的问题。
public
void
run() {
while ( ! dl.isOk()) {
// 暂停任务
synchronized ( this ) {
if (dl.isPaused()) {
try {
this .wait();
} catch (InterruptedException e) {
}
}
}
// 中断停止
if (Thread.interrupted() || dl.isStopped()) {
return ;
}
// 等待获取任务
Piece piece;
synchronized (tasks) {
while (tasks.isEmpty()) {
if (dl.isOk()) return ;
try {
tasks.wait();
// System.out.println(this.getName() + ":wait");
} catch (InterruptedException ie) {
// System.out.println(this.getName() +
// ":InterruptedException:" + ie.getMessage());
}
}
piece = tasks.remove( 0 );
dl.removeFreeLoader( this );
// System.out.println(this.getName() + ":loading");
}
try {
URL u = new URL(dl.getURL());
URLConnection uc = u.openConnection();
// 设置断点续传位置
uc.setAllowUserInteraction( true );
uc.setRequestProperty( " Range " , " bytes= " + piece.getPos() + " - " + piece.getEnd());
in = new BufferedInputStream(uc.getInputStream());
out = new RandomAccessFile(dl.getFileProcess(), " rw " );
out.seek(piece.getPos()); // 设置指针位置
long start;
long end;
int len = 0 ;
while (piece.getPos() < piece.getEnd()) {
start = System.currentTimeMillis();
len = in.read(buff, 0 , buff.length);
if (len == - 1 ) break ;
out.write(buff, 0 , len);
end = System.currentTimeMillis();
timeUsed += end - start; // 累计时间使用
long newPos = piece.getPos() + len;
// 如果该区段已经完成,如果该线程负责的区域已经完成,或出界
if (newPos > piece.getEnd()) {
piece.setPos(piece.getEnd());
long offset = newPos - piece.getEnd();
long trueReads = (len - offset + 1 );
dl.growReadBytes(trueReads); // 修正偏移量
dl.setOffsetTotal(dl.getOffsetTotal() + trueReads);
readBytes += trueReads;
// System.out.println(this.getName() + ":read=" + trueReads);
} else {
dl.growReadBytes(len);
piece.setPos(piece.getPos() + len);
readBytes += len;
// System.out.println(this.getName() + ":read=" + len);
}
// 如果存在空闲的任务线程,则切割出新的区域至任务队列中。由空闲
// 的线程辅助下载
if (dl.isFreeLoader()) {
Piece newPiece = piece.cutPiece();
if (newPiece != null ) {
synchronized (tasks) {
dl.addTask(newPiece);
dl.setRepairCount(dl.getRepairCount() + 1 ); // 增加切割次数
tasks.notifyAll(); // 唤醒等待任务中的空闲线程
}
}
}
// 暂停任务
synchronized ( this ) {
if (dl.isPaused()) {
try {
this .wait();
} catch (InterruptedException e) {
}
}
}
// 中断停止
if (Thread.interrupted() || dl.isStopped()) {
in.close();
out.close();
return ;
}
// System.out.println(this.getName() + ":read:" + dl.getReadBytes());
}
out.close();
in.close();
dl.addFreeLoader( this );
// System.out.println("切割次数:" + dl.getRepairCount());
if (dl.isOk()) dl.processWhenOk();
} catch (IOException e) {
// System.out.println(this.getName() + ":无法读取数据");
}
}
}
while ( ! dl.isOk()) {
// 暂停任务
synchronized ( this ) {
if (dl.isPaused()) {
try {
this .wait();
} catch (InterruptedException e) {
}
}
}
// 中断停止
if (Thread.interrupted() || dl.isStopped()) {
return ;
}
// 等待获取任务
Piece piece;
synchronized (tasks) {
while (tasks.isEmpty()) {
if (dl.isOk()) return ;
try {
tasks.wait();
// System.out.println(this.getName() + ":wait");
} catch (InterruptedException ie) {
// System.out.println(this.getName() +
// ":InterruptedException:" + ie.getMessage());
}
}
piece = tasks.remove( 0 );
dl.removeFreeLoader( this );
// System.out.println(this.getName() + ":loading");
}
try {
URL u = new URL(dl.getURL());
URLConnection uc = u.openConnection();
// 设置断点续传位置
uc.setAllowUserInteraction( true );
uc.setRequestProperty( " Range " , " bytes= " + piece.getPos() + " - " + piece.getEnd());
in = new BufferedInputStream(uc.getInputStream());
out = new RandomAccessFile(dl.getFileProcess(), " rw " );
out.seek(piece.getPos()); // 设置指针位置
long start;
long end;
int len = 0 ;
while (piece.getPos() < piece.getEnd()) {
start = System.currentTimeMillis();
len = in.read(buff, 0 , buff.length);
if (len == - 1 ) break ;
out.write(buff, 0 , len);
end = System.currentTimeMillis();
timeUsed += end - start; // 累计时间使用
long newPos = piece.getPos() + len;
// 如果该区段已经完成,如果该线程负责的区域已经完成,或出界
if (newPos > piece.getEnd()) {
piece.setPos(piece.getEnd());
long offset = newPos - piece.getEnd();
long trueReads = (len - offset + 1 );
dl.growReadBytes(trueReads); // 修正偏移量
dl.setOffsetTotal(dl.getOffsetTotal() + trueReads);
readBytes += trueReads;
// System.out.println(this.getName() + ":read=" + trueReads);
} else {
dl.growReadBytes(len);
piece.setPos(piece.getPos() + len);
readBytes += len;
// System.out.println(this.getName() + ":read=" + len);
}
// 如果存在空闲的任务线程,则切割出新的区域至任务队列中。由空闲
// 的线程辅助下载
if (dl.isFreeLoader()) {
Piece newPiece = piece.cutPiece();
if (newPiece != null ) {
synchronized (tasks) {
dl.addTask(newPiece);
dl.setRepairCount(dl.getRepairCount() + 1 ); // 增加切割次数
tasks.notifyAll(); // 唤醒等待任务中的空闲线程
}
}
}
// 暂停任务
synchronized ( this ) {
if (dl.isPaused()) {
try {
this .wait();
} catch (InterruptedException e) {
}
}
}
// 中断停止
if (Thread.interrupted() || dl.isStopped()) {
in.close();
out.close();
return ;
}
// System.out.println(this.getName() + ":read:" + dl.getReadBytes());
}
out.close();
in.close();
dl.addFreeLoader( this );
// System.out.println("切割次数:" + dl.getRepairCount());
if (dl.isOk()) dl.processWhenOk();
} catch (IOException e) {
// System.out.println(this.getName() + ":无法读取数据");
}
}
}
这里使用了RandomAccessFile进行保存本地文件,使用RandomAccessFile可以快速移动指针。另外一个就是需要在运行过程中,记得定时保存分片信息,以免在程序意外崩溃的情况下无法正常保存状态信息。
程序可能还存在一些不足并且还有很多可以改进的地方。
以下是状态文件Config的XML结构,这里记录了每个任务的运行状态,piece作为每个分段的状态,在程序重启之后载入这个配置文件,并在下载完成之后删除这一文件.另外主目录还有一个task.xml文件,用于记录所有下载任务,及状态文件的位置。
<?
xml version="1.0" encoding="UTF-8" standalone="no"
?>
< file id ="1207786769343_4046.6321122755676"
length ="3895507"
name ="读你 36首经典精选"
save ="C:\Documents and Settings\huliqing.TBUY-HULIQING\桌面\dist\musics\读你 36首经典精选[1].mp3"
threads ="12" >
< urls >
< url src ="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3" />
</ urls >
< pieces >
< piece end ="324626" pos ="20767" start ="0" />
< piece end ="649253" pos ="419439" start ="324627" />
< piece end ="973880" pos ="892414" start ="649254" />
< piece end ="1298507" pos ="1068702" start ="973881" />
< piece end ="1623134" pos ="1318124" start ="1298508" />
< piece end ="1947761" pos ="1706453" start ="1623135" />
< piece end ="2272388" pos ="1987815" start ="1947762" />
< piece end ="2597015" pos ="2535705" start ="2272389" />
< piece end ="2921642" pos ="2671690" start ="2597016" />
< piece end ="3246269" pos ="3176315" start ="2921643" />
< piece end ="3570896" pos ="3522551" start ="3246270" />
< piece end ="3895507" pos ="3678693" start ="3570897" />
</ pieces >
< file id ="1207786769343_4046.6321122755676"
length ="3895507"
name ="读你 36首经典精选"
save ="C:\Documents and Settings\huliqing.TBUY-HULIQING\桌面\dist\musics\读你 36首经典精选[1].mp3"
threads ="12" >
< urls >
< url src ="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3" />
</ urls >
< pieces >
< piece end ="324626" pos ="20767" start ="0" />
< piece end ="649253" pos ="419439" start ="324627" />
< piece end ="973880" pos ="892414" start ="649254" />
< piece end ="1298507" pos ="1068702" start ="973881" />
< piece end ="1623134" pos ="1318124" start ="1298508" />
< piece end ="1947761" pos ="1706453" start ="1623135" />
< piece end ="2272388" pos ="1987815" start ="1947762" />
< piece end ="2597015" pos ="2535705" start ="2272389" />
< piece end ="2921642" pos ="2671690" start ="2597016" />
< piece end ="3246269" pos ="3176315" start ="2921643" />
< piece end ="3570896" pos ="3522551" start ="3246270" />
< piece end ="3895507" pos ="3678693" start ="3570897" />
</ pieces >
- huliqing@huliqing.name
http://www.blogjava.net/huliqing/archive/2008/04/10/191725.html