Http多线程下载与断点续传分析

Http多线程下载与断点续传分析

    找了很多天的工作,真是找得有点郁闷,也就惰了下来!发现现在的简历真是不值钱。上次面试的时候本来投的是“高级程序员”职位,笔试也笔试,面试也面了。本来还是信心满满的. 不过后来在谈到薪水的时候,被贬到了初级程序员,给的高级程序员标准我还达不到,中级程序员的标准也需要一定条件--中级证书,郁闷的是我也没有!最后被定位为初级程序员!还真是有点打击人。

    不过我到现在也不知道初中高级程序员的标准究竟在哪里,平时总是尽量让自己提高,也很少去考滤到证书的重要性!总之最后就是泡汤了

    好了,口水就吐到这里吧,转入正题!投简历归投简历,剩余时间总是喜欢做一点自己喜欢做的事。

    有时候发现最快乐的事就是静静的听着音乐,敲着代码,写一些东西与朋友一起分享,一起研究。

    上次的 - “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 ;
        }

  程序根据线程数目划分成相应的分片信息(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() + ":无法读取数据");
            }
        }
    }

这里使用了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 >




- huliqing@huliqing.name

- http://www.huliqing.name



http://www.blogjava.net/huliqing/archive/2008/04/10/191725.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值