Java에서 중단점 다운로드 서버 및 클라이언트를 구현하는 방법
原理
首先,我们先说明了断点续传的功能,实际上的原理比较简单
客户端和服务端规定好一个规则,客户端传递一个参数,告知服务端需要数据从何处开始传输,服务端接收到参数进行处理,之后文件读写流从指定位置开始传输给客户端
实际上,上述的参数,在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에서 중단점 다운로드 서버 및 클라이언트를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제









Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

자바의 암스트롱 번호 안내 여기에서는 일부 코드와 함께 Java의 Armstrong 번호에 대한 소개를 논의합니다.

Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다
