Download:断点续传

首先说明:本文Java代码最初来源于网上,本人只是在原作的基础上修改。

概要:本例用于下载大容量的附件,可以启用多线程分段下载,掉线/请求暂停后可以继续下载。原理很简单,其实就是在本地目录下增加一个 *.info文件,以记录当前的位置;再次下载时读取这些信息,同时设置HttpURLConnection的头信息“User-Agent=NetFox; RANGE=bytes=xxx-”。

 

文档结构:

DownloadTask.java-主线程,启动Listener和Worker,下载完成删除info文件

DownloadListener.java-监听下载进度,当需要时写入info文件

DownloadWorker.java-下载一段内容

DownloadItem.java-下载对象

Main.java-测试程序。

 

------------------------------------

public class DownloadItem {
    private String siteURL;     // Site's URL  
    private String filePath;    // Saved File's Path  
 
    public DownloadItem(String webUrl, String localPath) {
        this.setSiteURL(webUrl);
        this.setFilePath(localPath);
    }
 
    public String getSiteURL() {
        return siteURL;
    }
    public void setSiteURL(String value) {
        siteURL = value;
    }
 
    public String getFilePath() {
        return filePath;
    }
    public void setFilePath(String value) {
        filePath = value;
    }
 
}

-----------------------------------

public class DownloadWorker extends Thread {
    private String sURL;            // File URL  
    private long startPos;          // File Snippet Start Position  
    private long endPos;            // File Snippet End Position  
    private FileAccess fileAccess;  // File Access interface
    private int size = 10240;       // data size
   
    boolean ifStop = false;         //客户端请求暂停或断线/IO等异常
    boolean isDownOver = false;     // download is successful
   
    private static Logger logger = Logger.getLogger("SiteFileFetch");
   
    public DownloadWorker(String sURL, String sName, long nStart, long nEnd,  
            int id) throws IOException {  
        super(String.valueOf(id));
        this.sURL = sURL;
        this.startPos = nStart;
        this.endPos = nEnd;
        fileAccess = new FileAccess(sName, startPos);
    }  
 
    public void run(){
        if (startPos >= endPos){
            isDownOver = true;
            return;
        }
       
        try {
            HttpURLConnection httpConnection = (HttpURLConnection)
                     new URL(sURL).openConnection();
            httpConnection.setRequestProperty("User-Agent", "NetFox");
            String sProperty = "bytes=" + startPos + "-";
            httpConnection.setRequestProperty("RANGE", sProperty);
            InputStream input = httpConnection.getInputStream();
            byte[] b = new byte[size];
            int nRead;
            while (startPos < endPos && !ifStop
                    && (nRead = input.read(b, 0, (int)Math.min(endPos-startPos, size))) > 0) {
                startPos += fileAccess.write(b, 0, nRead);
            }
            isDownOver = startPos >= endPos || !ifStop;
            if (isDownOver){
                System.out.println( "Thread " + this.getName() + " is over!");
            }else if (ifStop){
                System.out.println( "Thread " + this.getName() + " is paused!");
            }
        } catch (java.io.IOException e) {
            ifStop = true;
            throw new RuntimeException(e);
        }
    }  
 
    // 打印回应的头信息  
    public void logResponseHead(HttpURLConnection con) {
        for (int i=1; ; i++) {
            String header = con.getHeaderFieldKey(i);
            if (header != null){
                logger.log(Level.FINE, header + " : " + con.getHeaderField(header));
            }else{ 
                break;
            }
        }  
    }  
 
    public void requestStop() {
        this.ifStop = true;
    }

    public long getStartPos() {
        return startPos;
    }
    public long getEndPos() {
        return endPos;
    }
    public void setSize(int size){
        this.size = size;
    }
   
   
}

--------------------------------------

public class DownloadTask extends Thread {
    private DownloadItem downloadItem;    // 文件信息Bean
    private int ants;                     // 线程数
    private long[] startPos;              // 开始位置
    private long[] endPos;                // 结束位置
    private DownloadWorker[] workers;     // 子线程对象
    private boolean stopped = false;      // 停止标志
    private File tmpFile;                 // 文件下载的临时信息
   
    private long startTimeStamp = 0;
   
    public DownloadTask(DownloadItem bean, int ants) throws IOException {  
        downloadItem = bean;
        this.ants = ants;
        tmpFile = new File(bean.getFilePath() + ".info");
    }
 
    public void run() {
        try {
            if (!tmpFile.exists()) {
                if (this.splitFile()){
                    System.err.println("文件下载结束!");
                    return;
                }
            }else{
                this.read_nPos();
            }
           
            startTimeStamp = System.currentTimeMillis();
            // 启动子线程  
            workers = new DownloadWorker[startPos.length];
            for (int i = 0; i < workers.length; i++){
                workers[i] = new DownloadWorker(downloadItem.getSiteURL(),
                        downloadItem.getFilePath(), startPos[i], endPos[i], i);
                System.out.println("[Thread " + i + "], startPos = " + startPos[i]  
                        + ", endPos = " + endPos[i]);
                workers[i].start();
            }
           
            new DownloadListener().start();

            // 等待子线程返回
            while (!stopped) {
                sleep(500);
                if (this.isDownOver()){
                    stopped = true;
                    System.err.println("文件下载结束!");
                    System.err.println(System.currentTimeMillis()-startTimeStamp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
    // 停止文件下载
    public void pauseTask(){
        stopped = true;
        for (int i = 0; i < startPos.length; i++){
            workers[i].requestStop();
        }
    }
 
    // 获得文件长度  
    public long getFileSize() {
        int nFileLength = -1;
        try {
            URL url = new URL(downloadItem.getSiteURL());
            HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
            httpConnection.setRequestProperty("User-Agent", "NetFox");
            int responseCode = httpConnection.getResponseCode();
            if (responseCode >= 400){
                processErrorCode(responseCode);
                return -2; // can't access file
            }
            nFileLength = Integer.parseInt(
                    httpConnection.getHeaderField("Content-Length") );
        } catch (IOException e) {
            e.printStackTrace();
        } 
        return nFileLength;
    }
    // 获得当前完成的字节数
    public long getCurFinishedSize(){
        long sum = 0;
        for (DownloadWorker ant : workers){
            sum += (ant.getEndPos() - ant.getStartPos());
        }
        return sum;
    }
   
    // 将文件拆分N个线程,每个线程从不同的位置开始下载
    private boolean splitFile(){
        long fileLen = getFileSize(); // 获得Web站点文件长度
        File localFile = new File(downloadItem.getFilePath());
        if (localFile.exists()){
            if (localFile.getTotalSpace() >= fileLen){  //已经下载完成
                return true;
            }else{  //丢失了Info文件,重新下载
                localFile.delete();
            }
        }
       
        startPos = new long[ants];
        endPos = new long[ants];
        if (fileLen == -1) {
            System.err.println("File Length is not known!");
        } else if (fileLen == -2) {
            System.err.println("File is not access!");
        } else {
            long avg = fileLen / ants;
            for (int i = 0; i < ants; i++) {
                startPos[i] = (long) (i * avg);
            }  
            for (int i = 0; i < ants-1; i++) {
                endPos[i] = startPos[i + 1];
            }  
            endPos[ants-1] = fileLen;
        }
        return false;
    }
 
    // 读取保存的下载信息(文件指针位置)  
    private void read_nPos() {
        DataInputStream input = null;
        try {
            input = new DataInputStream(new FileInputStream(tmpFile));
            int nCount = input.readInt();
            startPos = new long[nCount];
            endPos = new long[nCount];
            for (int i = 0; i < startPos.length; i++) {
                startPos[i] = input.readLong();
                endPos[i] = input.readLong();
            }  
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if (input != null){
                try {
                    input.close();
                } catch (IOException e) {
                   
                }
                input = null;
            }
        }
    }  
 
    private void processErrorCode(int nErrorCode) {
        System.err.println("Error Code : " + nErrorCode);
    }
   
    //文件是否下载完成
    private boolean isDownOver(){
        int overs = 0;
        for (DownloadWorker ant : workers){
            if (ant.isDownOver){
                overs ++;
            }
        }
        return overs == ants;
    }
   
    class DownloadListener extends Thread{
        private DataOutputStream output;      // 输出到文件的输出流
       
        public void run(){
            while (!stopped){
                boolean required = false;
                for (DownloadWorker ant : workers){
                    if (ant.ifStop){
                        required = true;
                        break;
                    }
                }
               
                if (required){
                    this.write_nPos();
                }
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           
            this.write_nPos();
           
            if (isDownOver()){
                tmpFile.delete();
            }
        }
        // 保存下载信息(文件指针位置)
        private void write_nPos() {
            try {
                output = new DataOutputStream(new FileOutputStream(tmpFile));
                output.writeInt(startPos.length);
                for (DownloadWorker ant : workers){
                    output.writeLong(ant.getStartPos());
                    output.writeLong(ant.getEndPos());
                }
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            } 
        }
    }
 
}

--------------------------------------------

public class Main {
   
    public static void main(String[] args){
        DownloadItem bean = new DownloadItem(  
                "http://localhost:88/cxf/server103_win32.exe", "E:/temp/server103_win32.exe");
        try {
            DownloadTask task = new DownloadTask(bean, 5);
            task.start();
//            Thread.sleep(10000);
//            task.pauseTask();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值