用 Java 实现断点续传 (HTTP) | java教程

用 Java 实现断点续传 (HTTP)

断点续传是一种实现文件下载的机制,可以在中断下载后从上次下载的地方继续下载。在 HTTP 请求中实现断点续传与常规下载有所不同。

假设我们的目标是使用 Java 实现断点续传功能,以下是关键要点:

断点续传原理

当浏览器请求从服务器下载文件时,通常会发起以下请求:

GET /down.zip HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Connection: Keep-Alive

服务器接收到请求后,会寻找并提取文件信息,并返回给浏览器相应的信息,例如:

200
Content-Length=106786028
Accept-Ranges=bytes
Date=Mon, 30 Apr 2001 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

为了实现断点续传,客户端浏览器需要向 Web 服务器发送一个带有起始位置的请求信息。例如,下面的请求表明从字节位置 2000070 开始下载:

GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

请注意,增加了一行 `RANGE: bytes=2000070-`,该行告诉服务器从指定的字节位置开始传输文件。

服务器接收到这个请求后,返回带有范围的信息,如下所示:

206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

相比之前的响应,增加了一行 `Content-Range=bytes 2000070-106786027/106786028`,并且状态码变为 206。

了解了上述原理后,我们可以开始编写 Java 代码实现断点续传。

Java实现断点续传的关键几点

在Java中实现断点续传功能并不复杂,下面将介绍其中的关键几点。

1. 提交 RANGE 头部信息

要实现断点续传,需要在请求中提交 RANGE 头部信息,指定从哪个字节位置开始下载文件。

使用Java的net包中的HttpURLConnection类可以方便地完成这个步骤。以下是示例代码:

URL url = new URL("http://www.sjtu.edu.cn/down.zip");
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
// 设置User-Agent
httpConnection.setRequestProperty("User-Agent", "NetFox");
// 设置断点续传的开始位置
httpConnection.setRequestProperty("RANGE", "bytes=2000070");
// 获得输入流
InputStream input = httpConnection.getInputStream();

以上代码创建了一个HTTP连接,并设置了"User-Agent"和"RANGE"头部字段,以指定起始位置为2000070字节进行断点续传。

2. 保存文件的方法

接下来是如何保存从输入流获取的字节流到文件中。在这里,我们使用Java的IO包中的RandomAccessFile类来实现保存文件的操作。

该类允许我们根据需要定位文件指针,并在指定位置开始写入字节流。以下是保存文件的示例代码:

RandomAccessFile oSavedFile = new RandomAccessFile("down.zip", "rw");
long nPos = 2000070;
// 定位文件指针到nPos位置
oSavedFile.seek(nPos);
byte[] b = new byte[1024];
int nRead;
// 从输入流中读入字节流,然后写入文件
while ((nRead = input.read(b, 0, 1024)) > 0) {
    oSavedFile.write(b, 0, nRead);
}

以上代码创建了一个RandomAccessFile对象,并使用`seek(nPos)`方法将文件指针定位到指定位置(例如2000070字节)。

然后,循环读取输入流中的字节流,并将其写入文件。

实现断点续传的过程可以总结为以下几个步骤:

  • 1. 提交 RANGE 头部信息以确定下载起始位置。

  • 2. 获取输入流,该流包含了从指定位置开始的字节流。

  • 3. 创建用于保存文件的输出流(例如RandomAccessFile)。

  • 4. 将输入流中的字节流读取到内存缓冲区。

  • 5. 将内存缓冲区中的字节写入到文件中。

  • 6. 重复以上两个步骤,直到完成文件的下载。

通过上述步骤,我们可以实现简单的断点续传功能。当然,在实际应用中可能需要更多的错误处理、线程控制和进度监测等功能来完善程序。

以上代码仅提供了基本的实现思路和方法,具体根据实际需求进行扩展和改进。

接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。

以下是提供有关断点续传内核的实现的整理完善的文章内容。

断点续传内核的实现

为了实现断点续传功能,我们使用了6个类,其中包括一个测试类。这些类分别是:

  • 1. SiteFileFetch.java:负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。

  • 2. FileSplitterFetch.java:负责部分文件的抓取。

  • 3. FileAccessI.java:负责文件的存储。

  • 4. SiteInfoBean.java:存储要抓取的文件的信息,如文件保存的目录、文件名和抓取文件的URL等。

  • 5. Utility.java:工具类,放置一些简单的方法。

  • 6. TestMethod.java:测试类。

以下是源程序:

// SiteFileFetch.java
package NetFox;

import java.io.*;
import java.net.*;

public class SiteFileFetch extends Thread {
    SiteInfoBean siteInfoBean = null; // 文件信息Bean
    long[] nStartPos; // 开始位置
    long[] nEndPos; // 结束位置
    FileSplitterFetch[] fileSplitterFetch; // 子线程对象
    long nFileLength; // 文件长度
    boolean bFirst = true; // 是否第一次取文件
    boolean bStop = false; // 停止标志
    File tmpFile; // 文件下载的临时信息
    DataOutputStream output; // 输出到文件的输出流

    public SiteFileFetch(SiteInfoBean bean) throws IOException {
        siteInfoBean = bean;
        tmpFile = new File(bean.getSFilePath() + File.separator + bean.getSFileName() + ".info");
        if (tmpFile.exists()) {
            bFirst = false;
            read_nPos();
        } else {
            nStartPos = new long[bean.getNSplitter()];
            nEndPos = new long[bean.getNSplitter()];
        }
    }

    public void run() {
        try {
            if (bFirst) {
                nFileLength = getFileSize();
                if (nFileLength == -1) {
                    System.err.println("File Length is not known!");
                } else if (nFileLength == -2) {
                    System.err.println("File is not accessible!");
                } else {
                    for (int i = 0; i < nStartPos.length; i++) {
                        nStartPos[i] = (long) (i * (nFileLength / nStartPos.length));
                    }
                    for (int i = 0; i < nEndPos.length - 1; i++) {
                        nEndPos[i] = nStartPos[i + 1];
                    }
                    nEndPos[nEndPos.length - 1] = nFileLength;
                }
            }

            fileSplitterFetch = new FileSplitterFetch[nStartPos.length];
            for (int i = 0; i < nStartPos.length; i++) {
                fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
                        siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),
                        nStartPos[i], nEndPos[i], i);
                Utility.log("Thread " + i + ", nStartPos = " + nStartPos[i] + ", nEndPos = " + nEndPos[i]);
                fileSplitterFetch[i].start();
            }

            boolean breakWhile = false;

            while (!bStop) {
                write_nPos();
                Utility.sleep(500);
                breakWhile = true;
                for (int i = 0; i < nStartPos.length; i++) {
                    if (!fileSplitterFetch[i].bDownOver) {
                        breakWhile = false;
                        break;
                    }
                }
                if (breakWhile)
                    break;
            }

            System.err.println("文件下载结束!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public long getFileSize() {
        int nFileLength = -1;
        try {
            URL url = new URL(siteInfoBean.getSSiteURL());
            HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
            httpConnection.setRequestProperty("User-Agent", "NetFox");

            int responseCode = httpConnection.getResponseCode();
            if (responseCode >= 400) {
                processErrorCode(responseCode);
                return -2; // -2 表示访问错误
            }

            String sHeader;

            for(int i=1;;i++) {
                sHeader=httpConnection.getHeaderFieldKey(i);
                if(sHeader!=null && sHeader.equals("Content-Length")) {
                    nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
                    break;
                } else if (sHeader == null) {
                    break;
                }
            }
            return nFileLength;
        } catch (Exception e) {
            e.printStackTrace();
            return -1; // -1 表示错误
        }
    }

    public void write_nPos() {
        try {
            output = new DataOutputStream(new FileOutputStream(tmpFile));

            output.writeLong(nFileLength);

            for (int i = 0; i < nStartPos.length; i++) {
                output.writeLong(fileSplitterFetch[i].nStartPos);
                output.writeLong(fileSplitterFetch[i].nEndPos);
            }
            output.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void read_nPos() {
        try {
            DataInputStream input = new DataInputStream(new FileInputStream(tmpFile));

            nFileLength = input.readLong();

            for (int i = 0; i < nStartPos.length; i++) {
                nStartPos[i] = input.readLong();
                nEndPos[i] = input.readLong();
            }
            input.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stopIt() {
        bStop = true;
    }

    public double getProcess() {
        double process = 0.0;
        long length = 0;
        for (int i = 0; i < nStartPos.length; i++) {
            length += fileSplitterFetch[i].length;
        }
        process = length * 1.0 / nFileLength * 100;
        return process;
    }

    private void processErrorCode(int nErrorCode) {
        System.err.println("Error Code : " + nErrorCode);
    }
}
// FileSplitterFetch.java
package NetFox;

import java.io.*;
import java.net.*;

public class FileSplitterFetch extends Thread {
    static final int BUFFER_SIZE = 1024;

    URL url; // 文件路径
    String sName; // 保存文件名
    long nStartPos; // 文件分段的开始位置
    long nEndPos; // 文件分段的结束位置
    int nThreadID; // 线程编号
    long length;
    boolean bDownOver = false; // 是否下载完成

    public FileSplitterFetch(String sURL, String sName, long nStart, long nEnd, int id) throws IOException {
        this.url = new URL(sURL);
        this.sName = sName;
        this.nStartPos = nStart;
        this.nEndPos = nEnd;
        this.nThreadID = id;
    }

    public void run() {
        FileOutputStream output = null;
        InputStream input = null;
        try {
            HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection ();
            httpConnection.setRequestProperty("User-Agent", "NetFox");
            String sProperty = "bytes=" + nStartPos + "-";
            httpConnection.setRequestProperty("RANGE", sProperty);

            Utility.log(sProperty);

            input = httpConnection.getInputStream();

            byte[] b = new byte[BUFFER_SIZE];
            long nRead = 0;
            output = new FileOutputStream(sName + "_" + nThreadID);

            while (nStartPos < nEndPos) {
                nRead = input.read(b, 0, BUFFER_SIZE);
                if (nRead == -1)
                    break;
                output.write(b, 0, (int) nRead);
                nStartPos += nRead;
                length += nRead;
            }

            bDownOver = true;

            Utility.log("Thread " + nThreadID + " is over!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (output != null)
                    output.close();
                if (input != null)
                    input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
// FileAccessI.java
package NetFox;

import java.io.*;

public class FileAccessI implements Serializable {
    RandomAccessFile oSavedFile;
    long nPos;

    public FileAccessI() throws IOException {
        this("", 0);
    }

    public FileAccessI(String sName, long nPos) throws IOException {
        oSavedFile = new RandomAccessFile(sName, "rw");
        this.nPos = nPos;
        oSavedFile.seek(nPos);
    }

    public synchronized int write(byte[] b, int nStart, int nLen) {
        int n = -1;
        try {
            oSavedFile.write(b, nStart, nLen);
            n = nLen;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return n;
    }

    public synchronized int read(byte[] b, int nStart, int nLen) {
        int n = -1;
        try {
            oSavedFile.seek(nPos);
            n = oSavedFile.read(b, nStart, nLen);
            if (n > 0) {
                nPos += n;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return n;
    }

    public void close() throws IOException {
        if (oSavedFile != null)
            oSavedFile.close();
    }
}
// SiteInfoBean.java
package NetFox;

public class SiteInfoBean {
    private String sSiteURL; // 文件下载地址
    private String sFilePath; // 保存文件路径
    private String sFileName; // 保存文件名
    private int nSplitter; // 分割线程数

    public SiteInfoBean(String sURL, String sPath, String sName, int nSpiltter) {
        this.sSiteURL = sURL;
        this.sFilePath = sPath;
        this.sFileName = sName;
        this.nSplitter = nSpiltter;
    }

    public String getSSiteURL() {
        return sSiteURL;
    }

    public void setSSiteURL(String value) {
        sSiteURL = value;
    }

    public String getSFilePath() {
        return sFilePath;
    }

    public void setSFilePath(String value) {
        sFilePath = value;
    }

    public String getSFileName() {
        return sFileName;
    }

    public void setSFileName(String value) {
        sFileName = value;
    }

    public int getNSplitter() {
        return nSplitter;
    }

    public void setNSplitter(int nCount) {
        nSplitter = nCount;
    }
}
// Utility.java
package NetFox;

public class Utility {
    public static void log(String msg) {
        System.out.println(msg);
    }

    public static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
// TestMethod.java
package NetFox;

import java.io.IOException;

public class TestMethod {
    public static void main(String[] args) throws IOException {
        SiteInfoBean bean = new SiteInfoBean("http://example.com/file.txt", "D:\\Downloads", "file.txt", 5);
        SiteFileFetch siteFileFetch = new SiteFileFetch(bean);
        siteFileFetch.start();
    }
}

以上是断点续传内核的实现代码。这些类配合使用,可以在下载大文件时实现断点续传功能。主要的类是SiteFileFetch,负责整个文件的抓取和控制内部线程(FileSplitterFetch类)。其他的类包括FileSplitterFetch、FileAccessI、SiteInfoBean、Utility,分别用于文件分段的抓取、文件的存储、文件信息的存储和一些工具方法。

使用上述代码,你可以创建一个SiteInfoBean对象,设置文件的URL、保存路径、文件名和分割线程数。然后创建SiteFileFetch对象并启动它,就可以开始断点续传下载了。

请注意,以上代码只是示例,需要根据实际情况进行适当的修改和扩展。

 原来地址:用 Java 实现断点续传 (HTTP) | java教程-Java-Toy模板网

你可能感兴趣的:(杂文,java,http,开发语言)