广告位联系
返回顶部
分享到

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

java 来源:互联网 作者:佚名 发布时间:2022-08-21 18:56:06 人浏览
摘要

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

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

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

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

原理

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

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

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

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

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

格式如下:

1

2

3

4

5

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进行编写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

/**

 * 断点下载文件

 *

 * @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的网络框架写的,需要先引用依赖

1

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

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);

    }

}

使用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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("下载成功");

    }

});


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://www.cnblogs.com/stars-one/p/16592706.html
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计