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 == -1break;
                    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

posted on 2008-04-10 08:38 huliqing 阅读(18861) 评论(27)  编辑  收藏 所属分类: Swing

评论

# re: Http多线程下载与断点续传分析[未登录] 2008-04-10 09:07 Frank

一直在看你的blog,今天又有新的收获 ,先谢谢了 哦
  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 09:16 huliqing

@Frank
呵呵,欢迎互相交流学习!  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2008-04-10 09:18 Frank

你的程序下载的时候很快,但合并文件的时候没有用到多线程,所以下载完毕后,文件不能立即就使用  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 09:28 ljwan12

提供下载速度,单靠多线程是不行的,比如你选择的一个下载源,他的速度本来就很慢,这样你即使使用很多线程连接,速度也不会有多大的提高。
我认为有两种方法可以提高:
(1)、使用P2P,但此对于你的这个估计没多大效果,因为这个小软件的使用人数不多,同时下载某一个文件的概率太小。
(2)、选择更快的下载源,你的程序是通过百度MP3搜索查找歌曲,你可以通过程序来判断某一个下载源的速度,选择最快的一个。本人认为这种实现对于你的这个程序比较有效。
迅雷下载速度很快,他结合了很多种技术。
本人的一点建议,仅供参考!!  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 09:35 huliqing

@Frank
我使用的是多个RandomAccessFile同时利用一个File对象写同一个文件,操作系统本身会同步对文件的写操作。在利用多个File对象写同一个文件时,遇到一些问题, 另外我认为速度的瓶颈主要还是来自网络,本地文件读写应该不会是什么大问题。:)  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 09:40 huliqing

@ljwan12
<urls>
<url src="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3"/>
</urls>
呵呵,我在配置文件中预留这一个,我想,在有可能的情况下让程序从多个下载源中进行文件下载。有时候我们会搜到很多同文件而不同URL的源地址。利用这个应该可以更好的提升速度  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 09:40 Paul Lin

多谢楼主分享!  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 10:01 netnova

中级程序员的标准也需要一定条件--中级证书
什么公司,这么土。  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2008-04-10 10:02 Samuel

1、在选择需要下载的歌曲的时候,不能支持双击加入下载列表。也没有右键,这样挺不方便的  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 10:11 太阳

继续关注楼主的作品, 大家一起出谋划策  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-10 14:48 ljwan12

@huliqing
你的想法,在其他的很多下载软件中就是这么实现的,但是这个实现起来就比较复杂了,你还得建立自己的后台服务器来保存这些资源的一些信息,另外还有一个更加重要的是你需要有一套很好的机制来判断是否是完全一样的!!!
可以参考一些开源下载软件的代码,他们实现的都比较好!比如电驴  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2008-04-10 18:47 huliqing

谢谢大家的意见,作这一个只是研究或者学习一些技术,还无意做大,只是与大家共同研究学习一下,毕竟个人能力,时间与精力还是有限,呵呵!  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2008-04-11 16:55 TY

那公司也是真够土了.  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-04-15 00:33 endswel

后台机制,其实也不是什么。 他们都是算软件的MD5码或者其他一些不可逆的算法。 不过对于大软件的MD5码,你会算死的。
不过也有解决方案。 截取你对软件的部分信息。 取软件的3~5段来进行计算。  回复  更多评论   

# 你好 2008-07-06 11:23 landwang

你好,最近在做一个项目。用到断点续传方面的东西,也在研究。希望和你探讨一下。huliqing@live.com是你msn地址吗?
我的msn:landwang2001@hotmail.com  回复  更多评论   

# re: Http多线程下载与断点续传分析 2008-08-16 07:14 pig

楼主考虑过网络断开以后又恢复会出现什么情况吗?期待答复......  回复  更多评论   

# re: Http多线程下载与断点续传分析 2009-07-24 13:16 liouxiao

现在百度的mp3搜索页面改了,不是在<a href="...">中直接给出下载链接,导致工具无法抓取到有用的下载地址。

可以使用Mozilla的Javascript库Rhino执行页面里内嵌的Javascript代码,得到最终的下载地址。需要改动的类是biz.tbuy.huliqing.jloading.ext.mp3.CodeFilter,改动的方法是getMp3Address

具体代码已经发给你的邮箱了。  回复  更多评论   

# re: Http多线程下载与断点续传分析 2009-07-25 15:56 huliqing

@liouxiao
谢谢.已经收到邮件  回复  更多评论   

# re: Http多线程下载与断点续传分析 2009-08-30 10:31 BLOVE

博主,你好,我是第一次来能不能把getMp3Address方法代码再分享下,谢谢.
  回复  更多评论   

# re: Http多线程下载与断点续传分析 2009-08-31 10:07 huliqing

@BLOVE
参照一下原来的getMp3Address,再比较一下百度现有页面代码,应该很快可以写出来,或者联系一下上面的liouxiao吧,已经有很长一段时间没有写这个软件,代码目前还真找不到在哪里了。呵呵!  回复  更多评论   

# re: Http多线程下载与断点续传分析 2009-12-11 16:19 Moryk

仔细看看,也是老帖了.想问楼主
这样会不会耗费服务器资源呢?多线程下载开多个SOCKET连接DNS,会影响很大的吧.  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2010-05-25 10:35 M

你好。。能发个demo给我学习学习吗?
myx8178633@163.com
谢了。。  回复  更多评论   

# re: Http多线程下载与断点续传分析 2012-01-09 17:11 VARCHAR

有这个demo没有 发一个吧 谢谢 QQ:874729211  回复  更多评论   

# re: Http多线程下载与断点续传分析 2012-03-22 14:41 zhagnming

毕业设计和你这个有关系 不错 参考一下 很好  回复  更多评论   

# re: Http多线程下载与断点续传分析[未登录] 2013-04-06 15:31 小天

尼玛,你程序不能用呢,我 win7
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)  回复  更多评论   

# re: Http多线程下载与断点续传分析 2013-04-18 12:56 梁翠怡

博主您好,最近在做一个项目。用到断点续传方面的东西。希望您能指导一下或发个完整源码给我,谢谢您,这是我的邮箱:2565776764@qq.com  回复  更多评论   

# re: Http多线程下载与断点续传分析 2013-08-02 10:31 rambo_xue

怎么搜不到东西  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

公告

文章原创,欢迎转载
——转载请注明出处及原文链接

随笔分类(60)

随笔档案(33)

最新评论

评论排行榜