天天看點

實作單線程的斷點下載下傳

/**
 * 實作單線程的斷點下載下傳
 */

public class HttpDownloadSingle implements Runnable
{
    // 響應狀态碼
    private String responseCode;
    // 響應頭資訊
    private Map<String, String> headers = new HashMap<String, String>();
    // 下載下傳事件處理類
    private DownloadEvent event;
    // 下載下傳檔案的url
    private String url;
    // 檔案儲存在本地的路徑
    private String outFilePath;
    // 緩沖區大小
    private int bufferSize;

    public HttpDownloadSingle(DownloadEvent event, String url, String outFilePath, int bufferSize)
    {
        this.event = event;
        this.url = url;
        this.outFilePath = outFilePath;
        this.bufferSize = bufferSize;
    }

    @Override
    public void run()
    {
        try
        {
            download();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }

    public void download() throws IOException
    {
        // 下載下傳的檔案儲存在本地的路徑
        File outFile = new File(outFilePath);
        long finishedSize = 0;
        if (outFile.exists())
            // 如果檔案已經存在,記錄檔案大小,即已下載下傳大小
            finishedSize = outFile.length();

        URL download_url = new URL(url);
        Socket socket = new Socket();
        // 擷取主機位址
        String host = download_url.getHost();
        // 擷取端口 如果url位址沒有指定端口 會傳回-1 給定一個預設端口号80
        int port = download_url.getPort() == -1 ? 80 : download_url.getPort();
        // 資料路徑
        String resourcePath = download_url.getPath();
        event.state("connecting " + host + ":" + port);
        // 設定read逾時
        socket.setSoTimeout(5000);
        // 連接配接伺服器 并且設定連接配接逾時
        socket.connect(new InetSocketAddress(host, port), 3000);
        event.state("connect successfully");

        try
        {
            // 生成一個request消息,用于檢視所下載下傳檔案大小和伺服器是否支援斷點下載下傳
            generateHttpRequest(socket, host, resourcePath, finishedSize);

            InputStream socketIn = socket.getInputStream();
            // 解析響應頭資訊
            analyseResponseHeader(socketIn, event);

            // 擷取要下載下傳檔案的大小
            long contentLength = getFileLength();

            // 如果已下載下傳的大小大于等于總的檔案大小 直接傳回
            if (contentLength <= finishedSize)
                return;

            // 如果已下載下傳大小不等于0 且狀态碼是200(表示伺服器不支援斷點下載下傳
            // Accept-Ranges: bytes也可以用來判斷伺服器是否支援斷點下載下傳) 也直接傳回
            if (finishedSize > 0 && "200".equals(responseCode))
                return;

            //206表示支援斷點下載下傳 其他狀态碼就抛出一個運作時異常
            if (responseCode.charAt(0) != '2')
                throw new RuntimeException("Unsupported response status code");

            byte[] buf = new byte[bufferSize];
            int n;
            FileOutputStream fos = new FileOutputStream(outFile, true);

            try
            {
                while (-1 != (n = socketIn.read(buf)))
                {
                    fos.write(buf, 0, n);
                    finishedSize += n;
                    //計算目前已下載下傳百分比
                    event.percent(finishedSize * 100 / contentLength);
                }
            }
            finally
            {
                if (fos != null)
                {
                    try
                    {
                        fos.close();
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }
        finally
        {
            socket.close();
        }
    }

    private String getHeader(String name)
    {
        return headers.get(name);
    }

    private int getIntHeader(String name)
    {
        return Integer.parseInt(getHeader(name).trim());
    }

    private long getFileLength()
    {
        long len = -1;
        try
        {
            len = getIntHeader("Content-Length");
            String[] parts = getHeader("Content-Range").split("/");
            if (parts.length > 1)
                len = Integer.parseInt(parts[1].trim());
            else
                len = -1;
        }
        catch (Exception e)
        {
        }
        return len;
    }

    private void analyseResponseHeader(InputStream socketIn, DownloadEvent event) throws IOException
    {
        String line = "";
        while (true)
        {
            int bt = socketIn.read();
            if (bt == '\r')
            {
                bt = socketIn.read();
                if (bt == '\n')
                {
                    //如果讀到空行,就結束解析
                    if ("".equals(line))
                        break;
                    //解析狀态行和頭資訊
                    addHeader2Map(line);
                    event.viewHeader(line);
                    line = "";
                }
            }
            else
                line += (char) bt;
        }
    }

    private void addHeader2Map(String line)
    {
        int index = line.indexOf(":");
        if (index > 0)
            headers.put(line.substring(0, index).trim(), line.substring(index + 1));
        else
            analyseResponseLine(line);
    }

    private void analyseResponseLine(String line)
    {
        String[] parts = line.split(" +");
        if (parts.length > 1)
            //擷取狀态碼
            responseCode = parts[1].trim();
    }

    private void generateHttpRequest(Socket socket, String host, String path, long startPos) throws IOException
    {
        PrintWriter writer = new PrintWriter(socket.getOutputStream());
        writer.println("GET " + path + " HTTP/1.1");
        writer.println("Host: " + host);
        writer.println("User-Agent: java");
        if (startPos > 0)
            //如果已下載下傳長度不為0 就通過Range頭把已下載下傳長度也一起發送給伺服器
            writer.println("Range: bytes=" + startPos + "-");
        writer.println("Connection: close");
        writer.println();
        writer.flush();
    }

    public interface DownloadEvent
    {
        void state(String message);

        void viewHeader(String header);

        void percent(long newValue);
    }
}
           
class DownloadProgress implements DownloadEvent
{
    long oldValue = -1;

    @Override
    public void percent(long newValue)
    {
        if (newValue > oldValue)
        {
            System.out.print("[" + newValue + "%]");
            oldValue = newValue;
        }
    }

    @Override
    public void state(String message)
    {
        System.out.println(message);
    }

    @Override
    public void viewHeader(String header)
    {
        System.out.println(header);
    }
}

/*
 * 6688768 bytes 87494656 bytes 121552303 bytes
 */
public class Main
{
    public static void main(String[] args) throws Exception
    {

        if (args.length < 1)
        {
            System.out.println("Usage: java class DownloadFileName");
            return;
        }
        FileInputStream fis = new FileInputStream(args[0]);
        BufferedReader fileReader = new BufferedReader(new InputStreamReader(fis));
        String s = "";
        String[] ss;
        while ((s = fileReader.readLine()) != null)
        {
            ss = s.split("[ ]+");
            if (ss.length > 2)
            {
                System.out.println("---------------------------");
                System.out.println("downloading:" + ss[0]);
                System.out.println("output file path:" + ss[1]);
                System.out.println("buffer size:" + ss[2]);
                System.out.println("---------------------------");
                HttpDownloadSingle httpDownload = new HttpDownloadSingle(new DownloadProgress(), ss[0], ss[1], Integer
                        .parseInt(ss[2]));
                new Thread(httpDownload).start();
            }
        }
        fileReader.close();
    }
}
           

運作結果

---------------------------
downloading:http://localhost:8080/test_web/xx.exe
output file path:src/xx.exe
buffer size:4096
---------------------------
connecting localhost:8080
connect successfully
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"607195608-1397026780797"
Last-Modified: Wed, 09 Apr 2014 06:59:40 GMT
Content-Type: application/octet-stream
Content-Length: 607195608
Date: Wed, 16 Apr 2014 01:42:20 GMT
Connection: close
[0%][1%][2%][3%][4%][5%][6%][7%][8%][9%][10%][11%][12%][13%][14%][15%][16%][17%][18%]