斷點續傳(代碼實現)

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");
    }
}html

 

============================================================================java

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();    
    }
}瀏覽器

相關文章
相關標籤/搜索