这个版本中加入了断点续传的功能,使用了XML文件保存任务列表及状态信息,并且支持多线程分段下载, 提高下载速度,在这一个版本中,我把它叫做: JLoading 因为我还想不出一个更好听一点或更酷一些的名字,而且我还想让他可以下载一些其它文件。

上次的 - “Mp3在线搜索工具”还有很多可以改进的地方,也得到一些朋友的建议,非常感谢。这个版本中加入了断点续传的功能,使用了XML文件保存任务列表及状态 信息,并且支持多线程分段下载, 提高下载速度,在这一个版本中,我把它叫做: JLoading 因为我还想不出一个更好听一点或更酷一些的名字,而且我还想让他可以下载一些其它文件。程序不想做大,但想做得极致一些,比如从速度上。欢迎交流学习, huliqing(huliqing@live.com)

协议针对于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>

评论:

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

转载于:https://www.cnblogs.com/NeuqUstcIim/archive/2008/08/19/1271554.html

java下的Http多线程下载与断点续传分析【转自酷勤网】相关推荐

  1. android 多线程下载,断点续传,线程池

    android 多线程下载,断点续传,线程池 你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader 效果图 这张效 ...

  2. 【Android】多线程下载加断点续传

    http://blog.csdn.net/smbroe/article/details/42270573 文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大 ...

  3. android多线程下载程序卡死,android 多线程下载与断点续传

    多线程下载: 下载速度更快,服务器对每个线程平分资源,故线程越多,得到的资源越多,下载速度越快. 断点续传: 下载中断,再次下载时从上一次下载结束的位置开始下载,防止重复下载 下载结束后 代码: pa ...

  4. Java实现大文件多线程下载,提速30倍!想学?我教你啊

    前言 在上一篇文章 <面试官不讲武德>对Java初级程序猿死命摩擦Http协议 中,我们有提到大文件下载和断点续传,本篇我们就来开发一个多线程文件下载器,最后我们用这个多线程下载器来突破云 ...

  5. android线程池断点续传,Android之多线程下载及断点续传

    今天我们来接触一下多线程下载,当然也包括断点续传,我们可以看到 很多下载器,当开通会员的时候下载东西的速度就变得快了许多,这是为什么呢?这就是跟今天讲的多线程有关系了,其实就是多开了几个线程一起下载罢 ...

  6. Android 多线程下载以及断点续传

    多线程下载 在日常开发中,我们不可避免的会接到类似这样的需求,下载一个比较大的素材文件或者安装包文件,以此实现APP的自动更新,APP内的素材替换等.由于一般此类文件都比较大,一般会在50M以上,如果 ...

  7. 【Java】网络编程——多线程下载文件

    前言 多线程下载文件,比单线程要快,当然,线程不是越多越好,这和获取的源文件还有和网速有关. 原理:在请求服务器的某个文件时,我们能得到这个文件的大小长度信息,我们就可以下载此长度的某一个片段,来达到 ...

  8. Java通过URL进行多线程下载

    DownloadUtil 自定义工具类通过URL进行多线程下载 package com.jtc;import java.io.File; import java.io.FileInputStream; ...

  9. java httpclient 断点续传_【幻化万千戏红尘】qianfengDay27-HttpURLConnection,OkHttpClient,,多线程下载且断点续传基础学习:...

    课程回顾: Servlet:java语言开发的运行在服务器上的 开发步骤: 1.创建Servlet类 2.重写doGet或doPost方法 3.运行在服务器 生命周期: 1.初始化 2.服务 3.销毁 ...

  10. java jdk 7 64位_jdk1.7 64位下载-jdk7 64位(Java SE Development Kit 7)下载 7u80 官方正式版-IT猫扑网...

    jdk7 64位(java SE Development Kit 7)是一个适合java开发人员安装使用的运行环境,jdk应用于很多开发软件产品,这里为大家分享的是jdk1.7 64位的完整安装包,需 ...

最新文章

  1. 在厕所遇到领导到底该说些什么?
  2. MIT警示“深度学习过度依赖算力”,研究三年算法不如用10倍GPU
  3. 线性求逆元模板_ACM 数论基本模板
  4. 全程图解交换机和路由器的应用
  5. WPF 去除头部,实现拖动
  6. 按键精灵打怪学习-自动寻路回打怪点
  7. html注册页面带验证码,登陆注册-带图片的验证码
  8. 小米生态链成功的12个关键因素
  9. linux ubi 分区,linux UBI文件系统简介
  10. php模拟登录强智教务,湖南强智科技教务系统python模拟登录并爬取成绩(财院)...
  11. 2018.7.18 上半年课程总结 4- 高级英语
  12. 如何优化淘宝直通车推广创意标题
  13. 初探Java反序列化漏洞
  14. Clickhouse打包aarch64二进制文件
  15. Yolo v3的学习
  16. 陕西延安一男子看钓鱼忘拉手刹,爱车溜进鱼塘:自己都惊呆了
  17. 中南大学实验室安全知识 网上学习6小时的小工具,小技巧
  18. 这篇讲MySQL海量数据分库分表的,实在太过瘾了!
  19. 微信多平台版本日志大全.2021-12-17
  20. 视频文件M3U8和TS格式切片,讨论一下?

热门文章

  1. 银河麒麟v10离线安装docker-ce
  2. 计算机操作系统的最基本特性,操作系统有哪几大特征?其最基本的特征是什么?...
  3. 问脉首创旁路云原生安全检测框架!
  4. 四、Raid卡(阵列卡)
  5. CentOS安装NTFS-3G读写Windows 10的移动NTFS磁盘
  6. Java Web实训项目:西蒙购物网1
  7. 雕虫小技也重要--数据处理中的电子表格技巧
  8. c盘扩容提示簇被标记_如何解决C盘爆满
  9. 计算机图形学中点画线法
  10. mib文件在服务器的什么位置,MIB文件简单分析