


Comment implémenter le serveur et le client de téléchargement de points d'arrêt en 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("下载成功"); } });
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Guide de la racine carrée en Java. Nous discutons ici du fonctionnement de Square Root en Java avec un exemple et son implémentation de code respectivement.

Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

Guide du générateur de nombres aléatoires en Java. Nous discutons ici des fonctions en Java avec des exemples et de deux générateurs différents avec d'autres exemples.

Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

Guide du numéro Armstrong en Java. Nous discutons ici d'une introduction au numéro d'Armstrong en Java ainsi que d'une partie du code.

Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est
