Java 断点下载(下载续传)服务端及客户端(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 断点下载(下载续传)服务端及客户端(Android)代码的更多相关文章
- JAVA中Socket的用法模拟服务端和客户端
<看透springMvc源代码分析与实践>学习笔记 Socket分为ServerSocket和Socket两个大类 ServerSocket用于服务端,可以通过accept方法监听请求,监 ...
- Photon Server 实现注册与登录(五) --- 服务端、客户端完整代码
客户端代码:https://github.com/fotocj007/PhotonDemo_Client 服务端代码:https://github.com/fotocj007/PhotonDemo_s ...
- PHP 文件上传服务端及客户端配置参数说明
文件上传服务器端配置: ·file_uploads = On, 支持HTTP上传 ·upload_tmp_dir = , 临时文件保存的目录 ·upload_max_filesize=2M, 允许上传 ...
- Java的oauth2.0 服务端与客户端的实现
oauth原理简述 oauth本身不是技术,而是一项资源授权协议,重点是协议!Apache基金会提供了针对Java的oauth封装.我们做Java web项目想要实现oauth协议进行资源授权访问,直 ...
- 用Java实现HTTP Multipart的服务端和客户端
今天简单介绍一下如何用Java支持HTTP Multipart的request和response. 整个项目的代码可以在https://github.com/mcai4gl2/multi下载. 在这个 ...
- java版gRPC实战之三:服务端流
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- C# Socket服务端与客户端通信(包含大文件的断点传输)
步骤: 一.服务端的建立 1.服务端的项目建立以及页面布局 2.各功能按键的事件代码 1)传输类型说明以及全局变量 2)Socket通信服务端具体步骤: (1)建立一个Socket (2)接收 ...
- Java TCP服务端向客户端发送图片
/** * 1.创建TCP服务端,TCP客户端 * 2.服务端等待客户端连接,客户端连接后,服务端向客户端写入图片 * 3.客户端收到后进行文件保存 * @author Administrator * ...
- 一些java考过的测试题和自己制作模拟服务端和客户端
媒体 1,java环境变量: PATH: .;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; CLASSPATH: .;%JAVA_HOME%\jre\lib\rt.jar ...
随机推荐
- C++编码规范(本人自定义)
C++编码规范 1.变量名用camelCase命名法(即lowerCamelCase,小驼峰拼写法)命名. 即小写字母开头,如果变量名是复合词,第二个单词的首字母大写. 举例: int digitsC ...
- MySQL - 锁的分类
MySQL - 锁的分类 1. 加锁机制 乐观锁 悲观锁 2. 兼容性 共享锁 排他锁 3. 锁粒度 表锁 页锁 行锁 4. 锁模式 记录锁(record-lock) 间隙锁(gap-lock) ne ...
- android系统中有哪些日志
日志目录 android系统中还有很多常用的日志目录.我们可以通过adb命令把这些日志信息提取出来. data/system/dropbox data/system/usagestats data/s ...
- 『忘了再学』Shell基础 — 31、字符处理相关命令
目录 1.排序命令sort (1)sort命令介绍 (2)练习 2.取消重复行命令uniq 3.统计命令wc 1.排序命令sort (1)sort命令介绍 sort命令可针对文本文件的内容,以行为单位 ...
- SAP -熟练使用T-Code SHD0
SHD0 业务顾问和开发顾问都非常熟悉的一个T-Code, 如果能合理使用它,可以省去许多增强和程序修改工作. 当我需要时,我在这里找不到任何相关文档,这就是为什么我想借此机会向我们自己的SCN提供内 ...
- 使用navicat连接远程linux mysql数据库出现10061
重启mysql服务 两种方式 1.使用 service 启动:service mysql restart 2.使用 mysqld 脚本启动:/etc/inint.d/mysql restart
- NC14583 糖糖别胡说,我真的不是签到题目
NC14583 糖糖别胡说,我真的不是签到题目 题目 题目描述 从前,有 \(n\) 只萌萌的糖糖,他们分成了两组一起玩游戏.他们会排成一排,第 \(i\) 只糖糖会随机得到一个能力值 \(b_i\) ...
- CSS进阶内容—盒子和阴影详解
CSS进阶内容 在学习了CSS基本知识之后,我们需要进一步了解CSS,因此写下了这篇文章 当然如果没有学习之前的知识,可以到我的主页中查看之前的文章:秋落雨微凉 - 博客园 CSS三大特性 首先我们先 ...
- 如何用Python实现配置热加载?
背景 由于最近工作需求,需要在已有项目添加一个新功能,实现配置热加载的功能.所谓的配置热加载,也就是说当服务收到配置更新消息之后,我们不用重启服务就可以使用最新的配置去执行任务. 如何实现 下面我分别 ...
- Linux 启动流程及相关知识
基础知识 linux系统的组成 内核(kerner) 根文件系统(rootfs) 内核提供操作系统的功能,根文件系统包含常用的一些工具,这些工具.这些工具的运行离不开glibc库文件. 程序:二进制程 ...