用 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对象并启动它,就可以开始断点续传下载了。

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


文章来源地址https://www.toymoban.com/diary/java/362.html

到此这篇关于用 Java 实现断点续传 (HTTP) | java教程的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/java/362.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
上一篇 2023年10月07日 21:12
下一篇 2023年10月07日 21:54

相关文章

  • lazarus、delphi文件Http下载断点续传的实现

    下载大文件时,断点续传是很有必要的,特别是网速度慢且不稳定的情况下,很难保证不出意外,一旦意外中断,又要从头下载,会很让人抓狂。断点续传就能很好解决意外中断情况,再次下载时不需要从头下载,从上次中断处继续下载即可,这样下载几G或十几G大小的一个

    2024年02月13日
    浏览(52)
  • Spring Boot实现HTTP大文件断点续传分片下载-大视频分段渐进式播放

    服务端如何将一个大视频文件做切分,分段响应给客户端,让浏览器可以渐进式地播放。 Spring Boot实现HTTP分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文件到内存中。 文件的断点续传、文件多

    2024年02月14日
    浏览(70)
  • Spring-Boot实现HTTP大文件断点续传分片下载-大视频分段渐进式播放

    服务端如何将一个大视频文件做切分,分段响应给客户端,让浏览器可以渐进式地播放。 Spring Boot实现HTTP分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文件到内存中。 文件的断点续传、文件多

    2024年02月11日
    浏览(52)
  • Java实现文件断点续传

    文件断点续传代码 测试代码

    2024年03月09日
    浏览(38)
  • 【前端面试】中大文件上传/下载:中等文件代理服务器放行+大文件切片传输+并发请求+localstorage实现断点续传

    目录 切片上传~spark-md5 原理:流式计算+分块处理 文件标识spark-md5:A-B A.切片哈希值合并 B.首尾切片+其他切片前中后各取2M 计算hash:A-B(参考React的Fiber架构) A.线程:web-worker B.空闲:requestIdleCallback 异步并发控制:A-B(参考http2的多路复用) A.promise.allSettled() B.并发数max=

    2024年02月12日
    浏览(55)
  • U3D客户端框架之支持断点续传的文件下载器实现方案

            文件下载器是应用程序的基础模块,为应用程序与外部网络交互提供了必要的桥梁。该模块设计初衷是为了热更新过程中,下载CDN站点上的文件资源,所以下载器会验证 要下载的文件是否存在于CDN中。如果存在允许下载器继续工作;如果不存在会跳过本地下载。

    2024年02月07日
    浏览(52)
  • Java文件断点续传

    断点续传实现思路:将大文件均分成几块后,每个线程分别处理一块数据的读取和写入。每次写入都要更新记录的日志文件,断网或暂定后重新开始传输时,根据日志文件的信息,可以接着读取写入数据,不用重头开始传输。

    2024年02月12日
    浏览(39)
  • JAVA面试题分享五百一十一:Spring Boot基于WebUploader实现超大文件上传和断点续传

    目录 前言 目标 实现思路 大文件分片 合并分片 断点续传 代码实现 1、webuploader组件中,分片上传怎么开启? 2、webuploader组件中,文件的md5值如何计算? 3、webuploader组件中,分片文件的md5值如何计算? 4、webuploader组件中,分片上传的的请求在哪里触发? 5、前端、后端如何校

    2024年02月19日
    浏览(59)
  • 旧版Xcode文件较大导致下载总是失败但又不能断点续传重新开始的解决方法

    旧版mac下载旧版Xcode时需要进入https://developer.apple.com/download/all/?q=xcode下载,但是下载这些文件需要登录。登录后下载中途很容易失败,失败后又必须重新下载。 下载这里面的内容都需要登录,经过分析需提供其cookie,我这里使用的是motrix。需要下载时先通过搜索找到你要下载

    2024年02月15日
    浏览(68)
  • Vue项目中大文件切片上传实现秒传、断点续传的详细实现教程

    在Vue项目中,大图片和多数据Excel等大文件的上传是一个非常常见的需求。然而,由于文件大小较大,上传速度很慢,传输中断等问题也难以避免。因此,为了提高上传效率和成功率,我们需要使用切片上传的方式,实现文件秒传、断点续传、错误重试、控制并发等功能,并

    2024年02月12日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包