zoukankan      html  css  js  c++  java
  • Java实现的断点续传功能

    代码中已经加入了注释,需要的朋友可以直接参考代码中的注释。下面直接上功能实现的主要代码:

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /*
     * Encode:UTF-8
     * 
     * Author:zhiming.xu
     * 
     * 多线程的断点下载程序,根据输入的url和指定线程数,来完成断点续传功能。
     * 
     * 每个线程支负责某一小段的数据下载;再通过RandomAccessFile完成数据的整合。
     */
    public class MultiTheradDownLoad {
    
        private String filepath = null;
        private String filename = null;
        private String tmpfilename = null;
    
        private int threadNum = 0;
    
        private CountDownLatch latch = null;//设置一个计数器,代码内主要用来完成对缓存文件的删除
    
        private long fileLength = 0l;
        private long threadLength = 0l;
        private long[] startPos;//保留每个线程下载数据的起始位置。
        private long[] endPos;//保留每个线程下载数据的截止位置。
    
        private boolean bool = false;
    
        private URL url = null;
    
        //有参构造函数,先构造需要的数据
        public MultiTheradDownLoad(String filepath, int threadNum) {
            this.filepath = filepath;
            this.threadNum = threadNum;
            startPos = new long[this.threadNum];
            endPos = new long[this.threadNum];
            latch = new CountDownLatch(this.threadNum);
        }
    
        /*
         * 组织断点续传功能的方法
         */
        public void downloadPart() {
    
            File file = null;
            File tmpfile = null;
            HttpURLConnection httpcon = null;
    
            //在请求url内获取文件资源的名称;此处没考虑文件名为空的情况,此种情况可能需使用UUID来生成一个唯一数来代表文件名。
            filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath
                    .contains("?") ? filepath.lastIndexOf('?') : filepath.length());
            tmpfilename = filename + "_tmp";
    
            try {
                url = new URL(filepath);
                httpcon = (HttpURLConnection) url.openConnection();
    
                setHeader(httpcon);
                fileLength = httpcon.getContentLengthLong();//获取请求资源的总长度。
    
                file = new File(filename);
                tmpfile = new File(tmpfilename);
    
                threadLength = fileLength / threadNum;//每个线程需下载的资源大小。
                System.out.println("fileName: " + filename + " ," + "fileLength= "
                        + fileLength + " the threadLength= " + threadLength);
    
                if (file.exists() && file.length() == fileLength) {
                    System.out
                            .println("the file you want to download has exited!!");
                    return;
                } else {
                    setBreakPoint(startPos, endPos, tmpfile);
                    ExecutorService exec = Executors.newCachedThreadPool();
                    for (int i = 0; i < threadNum; i++) {
                        exec.execute(new DownLoadThread(startPos[i], endPos[i],
                                this, i, tmpfile, latch));
                    }
                    latch.await();//当你的计数器减为0之前,会在此处一直阻塞。
                    exec.shutdown();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            if (file.length() == fileLength) {
                if (tmpfile.exists()) {
                    System.out.println("delect the temp file!!");
                    tmpfile.delete();
                }
            }
        }
    
        /*
         * 断点设置方法,当有临时文件时,直接在临时文件中读取上次下载中断时的断点位置。没有临时文件,即第一次下载时,重新设置断点。
         * 
         * rantmpfile.seek()跳转到一个位置的目的是为了让各个断点存储的位置尽量分开。
         * 
         * 这是实现断点续传的重要基础。
         */
        private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
            RandomAccessFile rantmpfile = null;
            try {
                if (tmpfile.exists()) {
                    System.out.println("the download has continued!!");
                    rantmpfile = new RandomAccessFile(tmpfile, "rw");
                    for (int i = 0; i < threadNum; i++) {
                        rantmpfile.seek(8 * i + 8);
                        startPos[i] = rantmpfile.readLong();
    
                        rantmpfile.seek(8 * (i + 1000) + 16);
                        endPos[i] = rantmpfile.readLong();
    
                        System.out.println("the Array content in the exit file: ");
                        System.out.println("thre thread" + (i + 1) + " startPos:"
                                + startPos[i] + ", endPos: " + endPos[i]);
                    }
                } else {
                    System.out.println("the tmpfile is not available!!");
                    rantmpfile = new RandomAccessFile(tmpfile, "rw");
                    
                    //最后一个线程的截止位置大小为请求资源的大小
                    for (int i = 0; i < threadNum; i++) {
                        startPos[i] = threadLength * i;
                        if (i == threadNum - 1) {
                            endPos[i] = fileLength;
                        } else {
                            endPos[i] = threadLength * (i + 1) - 1;
                        }
    
                        rantmpfile.seek(8 * i + 8);
                        rantmpfile.writeLong(startPos[i]);
    
                        rantmpfile.seek(8 * (i + 1000) + 16);
                        rantmpfile.writeLong(endPos[i]);
    
                        System.out.println("the Array content: ");
                        System.out.println("thre thread" + (i + 1) + " startPos:"
                                + startPos[i] + ", endPos: " + endPos[i]);
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (rantmpfile != null) {
                        rantmpfile.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        /*
         * 实现下载功能的内部类,通过读取断点来设置向服务器请求的数据区间。
         */
        class DownLoadThread implements Runnable {
    
            private long startPos;
            private long endPos;
            private MultiTheradDownLoad task = null;
            private RandomAccessFile downloadfile = null;
            private int id;
            private File tmpfile = null;
            private RandomAccessFile rantmpfile = null;
            private CountDownLatch latch = null;
    
            public DownLoadThread(long startPos, long endPos,
                    MultiTheradDownLoad task, int id, File tmpfile,
                    CountDownLatch latch) {
                this.startPos = startPos;
                this.endPos = endPos;
                this.task = task;
                this.tmpfile = tmpfile;
                try {
                    this.downloadfile = new RandomAccessFile(this.task.filename,
                            "rw");
                    this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                this.id = id;
                this.latch = latch;
            }
    
            @Override
            public void run() {
    
                HttpURLConnection httpcon = null;
                InputStream is = null;
                int length = 0;
    
                System.out.println("the thread " + id + " has started!!");
    
                while (true) {
                    try {
                        httpcon = (HttpURLConnection) task.url.openConnection();
                        setHeader(httpcon);
                        
                        //防止网络阻塞,设置指定的超时时间;单位都是ms。超过指定时间,就会抛出异常
                        httpcon.setReadTimeout(20000);//读取数据的超时设置
                        httpcon.setConnectTimeout(20000);//连接的超时设置
    
                        if (startPos < endPos) {
                            
                            //向服务器请求指定区间段的数据,这是实现断点续传的根本。
                            httpcon.setRequestProperty("Range", "bytes=" + startPos
                                    + "-" + endPos);
    
                            System.out
                                    .println("Thread " + id
                                            + " the total size:---- "
                                            + (endPos - startPos));
    
                            downloadfile.seek(startPos);
    
                            if (httpcon.getResponseCode() != HttpURLConnection.HTTP_OK
                                    && httpcon.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
                                this.task.bool = true;
                                httpcon.disconnect();
                                downloadfile.close();
                                System.out.println("the thread ---" + id
                                        + " has done!!");
                                latch.countDown();//计数器自减
                                break;
                            }
    
                            is = httpcon.getInputStream();//获取服务器返回的资源流
                            long count = 0l;
                            byte[] buf = new byte[1024];
    
                            while (!this.task.bool && (length = is.read(buf)) != -1) {
                                count += length;
                                downloadfile.write(buf, 0, length);
                                
                                //不断更新每个线程下载资源的起始位置,并写入临时文件;为断点续传做准备
                                startPos += length;
                                rantmpfile.seek(8 * id + 8);
                                rantmpfile.writeLong(startPos);
                            }
                            System.out.println("the thread " + id
                                    + " total load count: " + count);
                            
                            //关闭流
                            is.close();
                            httpcon.disconnect();
                            downloadfile.close();
                            rantmpfile.close();
                        }
                        latch.countDown();//计数器自减
                        System.out.println("the thread " + id + " has done!!");
                        break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (is != null) {
                                is.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        /*
         * 为一个HttpURLConnection模拟请求头,伪装成一个浏览器发出的请求
         */
        private void setHeader(HttpURLConnection con) {
            con.setRequestProperty(
                    "User-Agent",
                    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
            con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
            con.setRequestProperty("Accept-Encoding", "aa");
            con.setRequestProperty("Accept-Charset",
                    "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            con.setRequestProperty("Keep-Alive", "300");
            con.setRequestProperty("Connection", "keep-alive");
            con.setRequestProperty("If-Modified-Since",
                    "Fri, 02 Jan 2009 17:00:05 GMT");
            con.setRequestProperty("If-None-Match", ""1261d8-4290-df64d224"");
            con.setRequestProperty("Cache-Control", "max-age=0");
            con.setRequestProperty("Referer",
                    "http://www.skycn.com/soft/14857.html");
        }
    }

     下面是测试代码:

    public class DownLoadTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            String filepath = "http://127.0.0.1:8080/file/loadfile.mkv";
            MultiTheradDownLoad load = new MultiTheradDownLoad(filepath ,4);    
            load.downloadPart();    
        }
    }

    参考资料:http://www.iteye.com/topic/427397这篇文章对断点续传的原理做了较详细的解释。

     

    代码中还有多处可以优化的地方,这里就弄了。断点续传的原理其实很简单,有兴趣的朋友可以多多了解!

  • 相关阅读:
    Codeforces 872B:Maximum of Maximums of Minimums(思维)
    Codeforces 849A:Odds and Ends(思维)
    洛谷 P2397:yyy loves Maths VI (mode)(摩尔投票算法)
    POJ 3264:Balanced Lineup(区间最值查询ST表&线段树)
    Atcoder ABC137D:Summer Vacation(贪心)
    POJ 3278:The merchant(LCA&DP)
    Codeforces Gym-100985C: MaratonIME plays Nim(交互题&博弈)
    ZOJ 1141:Closest Common Ancestors(LCA)
    浅谈C++运算符重载
    初学c++动态联编
  • 原文地址:https://www.cnblogs.com/xzmblog/p/4364832.html
Copyright © 2011-2022 走看看