Java实现断点下载服务端与客户端的示例代码

 更新时间:2022年08月21日 15:49:26   作者:Stars-one  
这篇文章主要为大家介绍了如何实现服务端(Spring Boot)与客户端(Android)的断点下载与下载续传功能,文中的示例代码讲解详细,需要的可以参考一下

最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一下关于贴上关于实现服务端(Spring Boot)与客户端(Android)是如何实现下载续传功能

断点下载功能(下载续传)解释:

客户端由于突然性网络中断等原因,导致的下载失败,这个时候重新下载,可以继续从上次的地方进行下载,而不是重新下载

原理

首先,我们先说明了断点续传的功能,实际上的原理比较简单

客户端和服务端规定好一个规则,客户端传递一个参数,告知服务端需要数据从何处开始传输,服务端接收到参数进行处理,之后文件读写流从指定位置开始传输给客户端

实际上,上述的参数,在http协议中已经有规范,参数名为Range

而对于服务端来说,只要处理好Range请求头参数,即可实现下载续传的功能

我们来看下Range请求头数据格式如下:

格式如下:

Range:bytes=300-800
//客户端需要文件300-800字节范围的数据(即500B数据)

Range:bytes=300-
//客户端需要文件300字节之后的数据

我们根据上面的格式,服务端对Range字段进行处理(String字符串数据处理),在流中返回指定的数据大小即可

那么,如何让流返回指定的数据大小或从指定位置开始传输数据呢?

这里,Java提供了RandomAccessFile类,通过seekTo()方法,可以让我们将流设置从指定位置开始读取或写入数据

这里读取和写入数据,我是采用的Java7之后新增的NIO的Channel进行流的写入(当然,用传统的文件IO流(BIO)也可以)

这里,我所说的客户端是指的Android客户端,由于App开发也是基于Java,所以也是可以使用RandomAccessFile这个类

对于客户端来说,有以下逻辑:

先读取本地已下载文件的大小,然后请求下载数据将文件大小的数据作为请求头的数值传到服务端,之后也是利用RandomAccessFile移动到文件的指定位置开始写入数据即可

扩展-大文件快速下载思路

利用上面的思路,我们还可以可以得到一个大文件快速下载的思路:

如,一份文件,大小为2000B(这个大小可以通过网络请求,从返回数据的请求头content-length获取获取)

客户端拿回到文件的总大小,根据调优算法,将平分成合适的N份,通过线程池,来下载这个N个单文件

在下载完毕之后,将N个文件按照顺序合并成单个文件即可

代码

上面说明了具体的思路,那么下面就是贴出服务端和客户端的代码示例

服务端

服务端是采用的spring boot进行编写

/**
 * 断点下载文件
 *
 * @return
 */
@GetMapping("download")
public void download( HttpServletRequest request, HttpServletResponse response) throws IOException {
    //todo 这里文件按照你的需求调整
    File file = new File("D:\\temp\\测试文件.zip");
    if (!file.exists()) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        return;
    }
    long fromPos = 0;
    long downloadSize = file.length();

    if (request.getHeader("Range") != null) {
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        String[] ary = request.getHeader("Range").replaceAll("bytes=", "").split("-");
        fromPos = Long.parseLong(ary[0]);
        downloadSize = (ary.length < 2 ? downloadSize : Long.parseLong(ary[1])) - fromPos;
    }
    //注意下面设置的相关请求头
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
    //相当于设置请求头content-length
    response.setContentLengthLong(downloadSize);

    //使用URLEncoder处理中文名(否则会出现乱码)
    response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("Content-Range", String.format("bytes %s-%s/%s", fromPos, (fromPos + downloadSize), downloadSize));

    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
    randomAccessFile.seek(fromPos);

    FileChannel inChannel = randomAccessFile.getChannel();
    WritableByteChannel outChannel = Channels.newChannel(response.getOutputStream());

    try {
        while (downloadSize > 0) {
            long count = inChannel.transferTo(fromPos, downloadSize, outChannel);
            if (count > 0) {
                fromPos += count;
                downloadSize -= count;
            }
        }
        inChannel.close();
        outChannel.close();
        randomAccessFile.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

客户端

Android客户端,是基于Okhttp的网络框架写的,需要先引用依赖

implementation 'com.squareup.okhttp3:okhttp:3.9.0'

下面给出的是封装好的方法(含进度,下载失败和成功回调):

package com.tyky.update.utils;

import com.blankj.utilcode.util.ThreadUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class FileDownloadUtil {

    public static void download(String url, File file, OnDownloadListener listener) {

        //http://10.232.107.44:9060/swan-business/file/download
        // 利用通道完成文件的复制(非直接缓冲区)
        ThreadUtils.getIoPool().submit(new Runnable() {
            @Override
            public void run() {
                try {

                    //续传开始的进度
                    long startSize = 0;
                    if (file.exists()) {
                        startSize = file.length();
                    }
                    OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
                    Request request = new Request.Builder().url(url)
                            .addHeader("Range", "bytes=" + startSize)
                            .get().build();
                    Call call = okHttpClient.newCall(request);
                    Response resp = call.execute();

                    double length = Long.parseLong(resp.header("Content-Length")) * 1.0;
                    InputStream fis = resp.body().byteStream();
                    ReadableByteChannel fisChannel = Channels.newChannel(fis);

                    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
                    //从上次未完成的位置开始下载
                    randomAccessFile.seek(startSize);
                    FileChannel foschannel = randomAccessFile.getChannel();

                    // 通道没有办法传输数据,必须依赖缓冲区
                    // 分配指定大小的缓冲区
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                    // 将通道中的数据存入缓冲区中
                    while (fisChannel.read(byteBuffer) != -1) {  // fisChannel 中的数据读到 byteBuffer 缓冲区中
                        byteBuffer.flip();  // 切换成读数据模式
                        // 将缓冲区中的数据写入通道
                        foschannel.write(byteBuffer);

                        final double progress = (foschannel.size() / length);
                        BigDecimal two = new BigDecimal(progress);
                        double result = two.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
                        //计算进度,回调
                        if (listener != null) {
                            listener.onProgress(result);
                        }
                        byteBuffer.clear();  // 清空缓冲区
                    }
                    foschannel.close();
                    fisChannel.close();
                    randomAccessFile.close();

                    if (listener != null) {
                        listener.onSuccess(file);
                    }
                } catch (IOException e) {
                    if (listener != null) {
                        listener.onError(e);
                    }

                }
            }
        });


    }

    public interface OnDownloadListener {
        void onProgress(double progress);

        void onError(Exception e);

        void onSuccess(File outputFile);
    }
}

使用:

FileDownloadUtil.download(downloadUrl, file, new FileDownloadUtil.OnDownloadListener() {
    @Override
    public void onProgress(double progress) {
        KLog.d("下载进度: " + progress);
    }

    @Override
    public void onError(Exception e) {
        KLog.e("下载错误: " + e.getMessage());
    }

    @Override
    public void onSuccess(File outputFile) {
        KLog.d("下载成功");
    }
});

到此这篇关于Java实现断点下载服务端与客户端的示例代码的文章就介绍到这了,更多相关Java断点下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 中通过 key 获取锁的方法

    Java 中通过 key 获取锁的方法

    这篇文章主要介绍了Java 中通过 key 获取锁,本文演示如何对某个 key 加锁,以保证对该 key 的并发操作限制,可以实现同一个 key 一个或者多个线程同时执行,需要的朋友可以参考下
    2022-11-11
  • SpringSecurity实现访问控制url匹配

    SpringSecurity实现访问控制url匹配

    本文主要介绍了SpringSecurity实现访问控制url匹配,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • springcloud结合bytetcc实现数据强一致性原理解析

    springcloud结合bytetcc实现数据强一致性原理解析

    这篇文章主要介绍了springcloud结合bytetcc实现数据强一致性原理解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 详解Spring注解@Validated 失效分析

    详解Spring注解@Validated 失效分析

    这篇文章主要介绍了Spring注解@Validated失效分析,文中有详细代码示例讲解了@Validated为何失效,需要的小伙伴可以参考一下
    2023-04-04
  • maven在IDEA下载依赖包速度慢的问题解决

    maven在IDEA下载依赖包速度慢的问题解决

    这篇文章主要介绍了maven在IDEA下载依赖包速度慢的问题解决方案,本文通过图文结合的形式给大家分享解决方案,供大家参考,需要的朋友可以参考下
    2024-01-01
  • 详解Java中的mapstruct插件使用

    详解Java中的mapstruct插件使用

    mapstruct 的插件是专门用来处理 domin 实体类与 model 类的属性映射的,我们只需定义 mapper 接口,mapstruct 在编译的时候就会自动的帮我们实现这个映射接口,避免了麻烦复杂的映射实现,对Java mapstruct使用相关知识感兴趣的朋友一起看看吧
    2022-04-04
  • Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    Java类加载器与双亲委派机制和线程上下文类加载器专项解读分析

    类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例的代码模块。本文主要和大家聊聊JVM类加载器ClassLoader的使用,需要的可以了解一下
    2022-12-12
  • Java Filter过滤器的使用教程

    Java Filter过滤器的使用教程

    Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能
    2023-01-01
  • vue 使用vuex在页面跳转的实现方式

    vue 使用vuex在页面跳转的实现方式

    这篇文章主要介绍了vue 使用vuex在页面跳转的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 解决SpringBoot框架因post数据量过大没反应问题(踩坑)

    解决SpringBoot框架因post数据量过大没反应问题(踩坑)

    这篇文章主要介绍了解决SpringBoot框架因post数据量过大没反应问题(踩坑),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09

最新评论