最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一下关于贴上关于实现服务端(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断点下载内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Java实现断点下载服务端与客户端的示例代码的更多相关文章

  1. iOS:调试无法在XCode中运行

    我正在使用XCode4和iOSSDK4.3.我的调试工作正常,但现在我发现在设置断点时,应用暂停,但XCode不关注编辑器中的行.点击断点时也不会显示绿色箭头.我有一个在AppDelegate中分配的UINavigationController.当我在didFinishLaunchingWithOptions中设置断点时,一切都按预期工作:这是当一个断点暂停了mapViewController中的

  2. ios – 异常断点处于活动状态时,应用程序在启动时崩溃

    我刚开始继续开发一款适用于商店的传统iPad应用程序.我注意到项目中的异常断点未启用.当我启用它时,应用程序在启动时崩溃,但在输出窗口中没有给出任何信息,而在线程视图中只有相当无用的信息(见下文)我试着解决它..>将Autolayout设置为关闭.>通过编辑和重新保存故事板文件..但到目前为止没有运气.我的猜测是,故事板中的某些内容被破坏了,因为AppDelegates“确实完成了启动……”

  3. xcode – 如何通过LLDB命令行添加断点操作?

    如果你从Xcode编辑一个断点,有一个超级有用的选项,可以添加一个“Action”,以便在每次遇到断点时自动执行.如何从LLDB命令行添加此类操作?

  4. xcode – 如何在LLDB断点条件下使用堆栈内容?

    问题:我有一种情况,我们在发布期间有媒体播放,并且objc_exception_throw()在此期间大约有5次点击,但总是被捕获,并且它在媒体播放器对象的南边.我厌倦了(a)必须手动连续n次,或者(b)在播放完成之前必须禁用断点.我尝试过的:>使断点忽略前五次命中(问题:它并不总是正好五次)>使用我的目标作为模块创建我自己的符号断点(问题:没有改变)我想做什么:想到的一个解决方案是在断点命中时评

  5. Xcode 4.3.1:在以下情况下不显示代码行:由于未捕获的异常而终止应用程序

    在Xcode的早期版本中,当我遇到崩溃时,Xcode通常会打开发生崩溃的文件,并经常将我带到崩溃的行.最新的Xcode似乎没有做那些让调试变得更难的东西.我是否需要启用一些新设置?

  6. ios – XCode断点应该只挂起当前线程

    我需要调试多线程错误.因此,为了获得生成崩溃的条件,我需要在代码中的特定点停止一个线程,并等待另一个线程到达第二个断点.我现在遇到的问题是,如果一个线程遇到断点,则所有其他线程都被挂起.有没有办法只停止一个线程,让其他线程运行,直到它们到达第二个断点?)其他更有趣的选择:当你点击第一个断点时,你可以进入控制台并写入这应该在该断点处暂停当前上下文中的线程一小时.然后在Xcode中恢复执行.

  7. 在Xcode中的选择器上添加符号断点

    我的应用程序中有一个错误,显示以下(部分)堆栈跟踪:为了调试这个,我决定在–[EventboolValue]上添加一个符号断点,推断当发送该选择器时,调试器将停止.然而,没有任何反应.设置断点后,应用程序只是士兵并生成相同的异常而不停止.我已经定义了断点如下:我正在使用LLDB调试器和Xcode4.2解决方法在选择器上设置断点会导致lldb在执行该选择器时停止,而不是在发送时停止.在你的情况下,没

  8. ios – 如何在Xcode 6中的异常断点上打印异常?

    我的应用程序正在崩溃,它似乎陷入了异常断点(这是有道理的),但我无法找到崩溃的原因.这是我尝试过的:>po$eax>po$rax>po$r0>po*(id*)($esp4)对于上述所有尝试,我收到以下错误:错误:使用未声明的标识符’$‘错误:解析表达式时出错1次我也找到了这个LLDBCommandGuide,但没有找到任何有用的东西(有点令人困惑,你不知道你在寻找什么)

  9. 从VS调试iOS时,程序’Mono’已从代码0(0x0)退出

    我有一个问题,当我尝试使用VS2013调试我的iOS应用程序时,我收到以下错误:Theprogram‘Mono’hasexitedwithcode0.我知道以下answer.清理解决方案确实解决了问题,但是下次调试时我需要再次执行…如果有人有长期解决方案,我们将非常感激.解决方法在发射期间它会崩溃吗?

  10. ios – Xcode中没有出现断点(9.3)

    st=jftp4n02&sh=49688058

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部