当前位置:

首页 > 编程开发 > Java怎么实现断点下载服务端与客户端

Java怎么实现断点下载服务端与客户端

原理首先,我们先说明了断点续传的功能,实际上的原理比较简单客户端和服务端规定好一个规则,客户端传递一个参数,告知服务端需要数据从何处开始传输,服务端接收到参数进行处理,之后文件读写流从指定位置开始传输给客户端实际上,上述的参数,在http协议中已经有规范,参数名为Range而对于服务端来说,只要处理好Range请求头参数,即可实现下载续传的功能我们来看下Range请求头数据格式如下:格式如下:Range:bytes=300-800//客户端需要文件300-800字节范围的数据(即500B数据)Range:

本文目录

    原理

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

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

    实际上,上述的参数,在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("下载成功");
        }
    });
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。