検索
ホームページJava&#&チュートリアル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 &#39;com.squareup.okhttp3:okhttp:3.9.0&#39;

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

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 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は亿速云で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。
高度なJavaプロジェクト管理、自動化の構築、依存関係の解像度にMavenまたはGradleを使用するにはどうすればよいですか?高度なJavaプロジェクト管理、自動化の構築、依存関係の解像度にMavenまたはGradleを使用するにはどうすればよいですか?Mar 17, 2025 pm 05:46 PM

この記事では、Javaプロジェクト管理、自動化の構築、依存関係の解像度にMavenとGradleを使用して、アプローチと最適化戦略を比較して説明します。

適切なバージョン化と依存関係管理を備えたカスタムJavaライブラリ(JARファイル)を作成および使用するにはどうすればよいですか?適切なバージョン化と依存関係管理を備えたカスタムJavaライブラリ(JARファイル)を作成および使用するにはどうすればよいですか?Mar 17, 2025 pm 05:45 PM

この記事では、MavenやGradleなどのツールを使用して、適切なバージョン化と依存関係管理を使用して、カスタムJavaライブラリ(JARファイル)の作成と使用について説明します。

カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか?カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか?Mar 17, 2025 pm 05:44 PM

この記事では、カフェインとグアバキャッシュを使用してJavaでマルチレベルキャッシュを実装してアプリケーションのパフォーマンスを向上させています。セットアップ、統合、パフォーマンスの利点をカバーし、構成と立ち退きポリシー管理Best Pra

キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPA(Java Persistence API)を使用するにはどうすればよいですか?キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPA(Java Persistence API)を使用するにはどうすればよいですか?Mar 17, 2025 pm 05:43 PM

この記事では、キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPAを使用することについて説明します。潜在的な落とし穴を強調しながら、パフォーマンスを最適化するためのセットアップ、エンティティマッピング、およびベストプラクティスをカバーしています。[159文字]

Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか?Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか?Mar 17, 2025 pm 05:35 PM

Javaのクラスロードには、ブートストラップ、拡張機能、およびアプリケーションクラスローダーを備えた階層システムを使用して、クラスの読み込み、リンク、および初期化が含まれます。親の委任モデルは、コアクラスが最初にロードされ、カスタムクラスのLOAに影響を与えることを保証します

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

MantisBT

MantisBT

Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強力な PHP 統合開発環境

EditPlus 中国語クラック版

EditPlus 中国語クラック版

サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。