解决方案 | 基于SFTP协议的文件传输断点续传Java实现方案

背景

因项目需要,我们服务每天都需要通过SFTP协议来对接上下游进行文件传输,但是对于一些大文件,在与第三方公司的服务器对接过程中很可能会因为网络问题或上下游服务器性能问题导致文件上传或者下载被中断,每次重试都需要重新对文件进行上传和下载,非常浪费带宽、服务器资源和时间,因此我们需要尽量提升文件传输效率,减少不必要的文件传输损耗。

解决思路

我们平时用一些下载软件,都有个断点续传功能,可以基于上一次已经传输的偏移量进行传输,不需要重复传输已经传输完整的数据,大大节省文件下载或者文件上传时间。

在通过SFTP进行文件传输,同样可以利用该原理进行断点续传。

文件上传原理

上传文件时,你首先需要与SFTP服务器建立一个安全会话(Session)。这需要提供用户名、密码、SFTP服务器的地址及端口。一旦会话建立,就可以打开一个SFTP通道(Channel)进行文件传输。

在处理大文件时,为了防止因网络问题导致的文件传输中断,以及减少不必要的重复传输,我们通常会采用断点续传的方式。这意味着如果文件传输在中途中断,下一次传输可以从上次结束的地方开始,而不是重新开始。

JSch库的put方法支持断点续传。通过检查远程文件的大小,你可以确定已经上传的数据量。然后,使用FileInputStream来打开本地文件,并使用skip方法跳过已上传的部分。最后,使用put方法的RESUME标志从上次中断的地方开始上传剩余的文件部分。

这种方法的好处是:

  • 节省时间:不需要重新上传已经传输过的部分。
  • 减少资源消耗:减少网络带宽的使用,特别是在网络不稳定或计费昂贵的环境中。
  • 提高可靠性:即使在传输过程中发生中断,也可以保证最终文件的完整性。

文件下载原理

下载文件的原理与上传类似。同样需要建立会话和打开SFTP通道。使用get方法从SFTP服务器下载文件。如果你需要实现断点续传下载,你需要检查本地文件的大小,以此来确定已经下载的数据量。

如果本地文件的大小小于远程文件的大小,说明下载尚未完成,你可以从本地文件的末尾开始继续下载。JSch的get方法同样支持RESUME标志,允许你指定从远程文件的某个位置开始下载。

断点续传下载的好处包括:

  • 节省时间:如果下载被中断,可以继续从中断点开始,而不是从头开始。
  • 减少资源消耗:只下载尚未接收的文件部分,节约网络带宽。
  • 提高可靠性:保证即使在网络不稳定情况下,也可以最终获取完整文件。

代码实现

这里使用了com.github.mwiede的Jsch版本,是基于Jcraft 0.1.55增加了一些新算法的支持。

<dependency>
    <groupId>com.github.mwiede</groupId>
    <artifactId>jsch</artifactId>
    <version>0.2.16</version>
</dependency>

文件上传断点续传实现:

加入SftpProgressMonitor可以更好监控文件传输的进度

package com.eshare.resumablesftp;

import com.jcraft.jsch.*;

import java.io.*;

public class SFTPResumeUpload {
    private static final int PORT = 22;

    public static void main(String[] args) {
        String user = "parallels";
        String passwd = "xxx";
        String host = "192.168.50.33";
        String localFilePath = "/Users/evan/Downloads/1080p.mp4";
        String remoteFilePath = "/tmp/evan/test10.mp4";

        try {
            // 设置JSch
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, PORT);
            session.setPassword(passwd);

            // 设置配置信息
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);

            // 连接到服务器
            session.connect();

            // 打开SFTP通道
            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;
            long remoteSize = 0;

            // 检查远程文件是否存在
            SftpATTRS attrs = sftpChannel.lstat(remoteFilePath);
            if (!attrs.isReg()) {
                throw new FileNotFoundException("Remote file does not exist: " + remoteFilePath);
            }
            // 检查远程文件大小
            remoteSize = attrs.getSize();
            
            // 打开本地文件
            RandomAccessFile raf = new RandomAccessFile(localFilePath, "r");

            // 计算从哪里开始上传
            long startPos = Math.max(0, remoteSize);
            raf.seek(startPos);

            // 文件上传
            long totalBytes = raf.length();
            OutputStream os = sftpChannel.put(remoteFilePath, new MyProgressMonitor(totalBytes - remoteSize), ChannelSftp.RESUME);
            byte[] buffer = new byte[1024 * 1024];//1M
            int bytesRead;
            while ((bytesRead = raf.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            raf.close();

            // 检查文件传输是否已经完成ÒÒ
            if (sftpChannel.lstat(remoteFilePath).getSize() == totalBytes) {
                System.out.println("File upload completed successfully.");
            } else {
                System.out.println("File upload failed.");
            }

            // 关闭连接
            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException | IOException | SftpException e) {
            e.printStackTrace();
        }
    }

    public static class MyProgressMonitor implements SftpProgressMonitor {
        private long totalBytes;
        private long transferredBytes = 0;

        public MyProgressMonitor(long totalBytes) {
            this.totalBytes = totalBytes;
        }

        @Override
        public void init(int op, String src, String dest, long max) {
            System.out.println("Starting transfer: " + src + " --> " + dest);
        }

        @Override
        public boolean count(long bytes) {
            transferredBytes += bytes;
            double percentage = (double) transferredBytes / totalBytes * 100;
            System.out.printf("Transferred %d of %d bytes (%.2f%%)\n", transferredBytes, totalBytes, percentage);
            return true;
        }

        @Override
        public void end() {
            System.out.println("\nTransfer complete.");
        }
    }

}

断点续传测试步骤

1.我本地放一个2.1G的测试文件

2.准备好远程目录,这里提前创建好一个测试目录在远程虚拟机/tmp/evan

3.启动程序,控制台会打印文件传输进度,文件传输到52%左右我把程序直接杀死来模拟网络中断或者传输中断的情况

4.重新启动程序,让程序自动从上一次传输的偏移量继续上传,大家可以尝试多次中断来模拟。

5.文件传输完成后,到远程目录对比文件大小,这里也可以通过文件checksum来进行对比,以下输出结果可以看到文件被成功上传。

文件下载断点续传实现

package com.eshare.resumablesftp;

import com.jcraft.jsch.*;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.*;
import java.security.MessageDigest;

public class SFTPResumeDownload {
    private static final int PORT = 22;

    public static void main(String[] args) {
        String user = "parallels";
        String passwd = "xxx";
        String host = "192.168.50.33";
        String localFilePath = "/Users/evan/Downloads/test10.mp4";
        String remoteFilePath = "/tmp/evan/test10.mp4";


        try {
            // 设置JSch
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, PORT);
            session.setPassword(passwd);

            // 设置配置信息
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);

            // 连接到服务器
            session.connect();

            // 打开SFTP通道
            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;

            // 检查远程文件是否存在
            SftpATTRS attrs = null;
            try {
                attrs = sftpChannel.lstat(remoteFilePath);
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    throw new FileNotFoundException("Remote file does not exist: " + remoteFilePath);
                }
                throw e;
            }

            // 检查本地文件大小
            long localSize = new File(localFilePath).length();

            // 打开远程文件
            long remoteSize = attrs.getSize();

            // 检查文件是否正常
            if (localSize >= remoteSize) {
                throw new FileSystemAlreadyExistsException("Local file exists and please check the size: " + remoteFilePath);
            }


            /// 计算从哪里开始下载
            long startPos = Math.max(0, localSize);


            // 文件下载
            FileOutputStream fos = new FileOutputStream(localFilePath, true);
            InputStream is = sftpChannel.get(remoteFilePath, new MyProgressMonitor(remoteSize - startPos), startPos);
            byte[] buffer = new byte[1024 * 1024];//1M
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            is.close();
            fos.close();

            // 检查文件下载是否已经完成
            if (new File(localFilePath).length() == remoteSize) {
                System.out.println("File download completed successfully.");
            } else {
                System.out.println("File download failed.");
            }

            // 关闭连接
            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException | IOException | SftpException e) {
            e.printStackTrace();
        }
    }

    public static class MyProgressMonitor implements SftpProgressMonitor {
        private long totalBytes;
        private long transferredBytes = 0;

        public MyProgressMonitor(long totalBytes) {
            this.totalBytes = totalBytes;
        }

        @Override
        public void init(int op, String src, String dest, long max) {
            System.out.println("Starting transfer: " + src + " --> " + dest);
        }

        @Override
        public boolean count(long bytes) {
            transferredBytes += bytes;
            double percentage = (double) transferredBytes / totalBytes * 100;
            System.out.printf("Downloaded %d of %d bytes (%.2f%%)\n", transferredBytes, totalBytes, percentage);
            return true;
        }

        @Override
        public void end() {
            System.out.println("\nTransfer complete.");
        }
    }
}



断点续传测试步骤

1.我远程放一个2.1G的测试文件

parallels@ubuntu-linux-22-04-desktop:/tmp/evan$ ls -lh test10.mp4 
-rw-rw-r-- 1 parallels parallels 2.1G Jan 23 11:15 test10.mp4

2.准备好本地目录,这里是我本机下载目录/Users/evan/Downloads/
3.启动程序,控制台会打印文件传输进度,文件传输到86%左右我把程序直接杀死来模拟网络中断或者传输中断的情况

4.重新启动程序,让程序自动从上一次传输的偏移量继续上传,大家可以尝试多次中断来模拟。

5.文件传输完成后,到远程目录对比文件大小,这里也可以通过文件checksum来进行对比,以下输出结果可以看到文件被成功上传。

evan@EvandeMBP Downloads % ls -lh test10.mp4 
-rw-r--r--  1 evan  staff   2.1G Jan 23 14:39 test10.mp4
evan@EvandeMBP Downloads % 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/344876.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

骨传导是表示啥?骨传导耳机的工作原理是什么

近期&#xff0c;骨传导耳机这种创新的产品引发了市场关注&#xff0c;也引起了大量用户的疑问&#xff1a;骨传导是表示啥&#xff1f;骨传导的工作原理是什么&#xff1f;下面让我们来详细解析一下骨传是表示啥&#xff1f;以及骨传导耳机的工作原理是什么。 骨传导是表示啥&…

在Word中插入高亮/好看代码

Md2All 一个markdown工具 Md2All 网址 代码一定要用code 高亮主题可选 atom-one-light > 复制到word > 调节字体可选Cnsolas, 间距等 效果 另一个高亮工具 效果

集简云新增邮件发送功能,适用多种创意场景并提升邮件发送效率

在数字营销中&#xff0c;电子邮件依旧是连接企业与客户的重要桥梁。集简云深知这一点&#xff0c;本周推出为企业通讯打造的内置应用——集简云邮件发送&#xff0c;帮助用户创建充满个性化的交易电子邮件&#xff0c;还能通过HTML自定义代码来实现用户的创意场景。可与近千款…

多态应用实例

目录 多态优点1.模拟冲泡饮品过程2.电脑组装 多态优点 多态的优点&#xff1a; 代码组织结构清晰&#xff1b;可读性强&#xff1b;利于前期和后期的扩展以及维护。 如果想扩展新的功能&#xff0c;不需要修改源码&#xff0c;遵循开发中开闭原则&#xff0c;只需在补充所需…

yml配置文件怎么引用pom.xml中的属性

目录 前言配置测试 前言 配置文件中的一些参数有时要用到pom文件中的属性&#xff0c;做到pom文件变配置文件中也跟着变&#xff0c;那如何才能做到呢&#xff0c;下面咱们来一起探讨学习。 配置 1.首先要在pom.xml中做如下配置&#xff0c;让maven渲染src/main/resources下配…

性能优化(CPU优化技术)-NEON 介绍

「发表于知乎专栏《移动端算法优化》」 本节主要介绍基本 SIMD 及其他的指令流与数据流的处理方式&#xff0c;NEON 的基本原理、指令以及与其他平台及硬件的对比。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;…

【力扣】记录一下竞赛分上 Knight

记录一下力扣上 Knight 力扣的题还是相对来说比较简单的&#xff0c;前两个月写的题多一点&#xff0c;后面几乎都是只做了每日一题&#xff0c;感觉正常来说刷个两三个月的题水平就差不多够了&#xff0c;甚至在我才刷半个月的时候就可以做三题了&#xff0c;排名和现在差不多…

vue3源码(二)reactiveeffect

一.reactive与effect功能 reactive方法会将对象变成proxy对象&#xff0c; effect中使用reactive对象时会进行依赖收集&#xff0c;稍后属性变化时会重新执行effect函数。 <div id"app"></div><script type"module">import {reactive,…

2024年需要重点关注的15种计算机病毒

2024年&#xff0c;计算机病毒威胁变得愈发多元化和复杂化。涉及勒索病毒、二维码病毒、挖矿木马等15种类型&#xff0c;这些病毒从数据勒索到系统入侵&#xff0c;对全球网络安全构成严峻挑战。 2024年&#xff0c;计算机病毒威胁变得愈发多元化和复杂化。涉及勒索病毒、二维码…

Java 报错java.Net.UnknownHostException:raw.githubusercontent.com

1.问题 今天在vscode 学习如何使用 plantUML生成图片的时候&#xff0c;发生错误 java.util.concurrent.ExecutionException: java.net.UnknownHostException: raw.githubusercontent.com issue raw.githubusercontent.com java.util.concurrent.ExecutionException: java.n…

vulhub之Zabbix篇

CVE-2016-10134--SQL注入 一、漏洞介绍 zabbix是一款服务器监控软件&#xff0c;其由server、agent、web等模块组成&#xff0c;其中web模块由PHP编写&#xff0c;用来显示数据库中的结果。 漏洞环境 在vulhub靶场进行复现&#xff0c;启动zabbix 3.0.3。 二、复现步骤 1…

JL-03-Q6 校园气象站

产品概述 校园气象站针对测量与环境、科学研究等相关的气象指标进行设计制造&#xff0c;气象站对采集数据信息以图表、数据的形式真实、直观的反应当前环境数据指标。可通过各种传感器对气压、气温、相对湿度、风向、风速、雨量、太阳辐射、乃至空气质量等要素进行采集、存储…

Type-C平板接口协议芯片介绍,实现单C口充放电功能

在现代平板电脑中&#xff0c;Type-C接口已经成为了一个非常常见的接口类型。相比于传统的USB接口&#xff0c;Type-C接口具有更小的体积、更快的传输速度和更方便的插拔体验。但是&#xff0c;在使用Type-C接口的平板电脑上&#xff0c;如何实现单C口充电、放电和USB2.0数据传…

【后端技术】术有千法,道本归一

目录 1.概述 2.机器的问题 2.1.计算 2.2.存储 2.3.传输 3.人的问题 3.1.代码工程的管理 3.2.过程的把控 4.总结 1.概述 术有千法&#xff0c;道本归一。 之所以这样说&#xff0c;是因为当前出现的纷繁复杂的后端技术&#xff0c;其本质其实都是为了解决同一套问题。…

C++:迭代器失效问题

目录 1.vector迭代器失效问题 1.底层空间改变 ​编辑 2.指定位置元素的删除操作 2.Linux下的迭代器失效检测 1.扩容 2.删除 3.解决方法 1.vector迭代器失效问题 迭代器的主要作用就是让算法能够不用关心底层数据结构&#xff0c;其底层实际就是一个指针&#xff0c;或者是…

uniapp设置隐藏原生导航栏(3)

1、单个页面隐藏 在pages.json里配置 (第一种方式) {"path": "pages/home/index","style": {"navigationBarTitleText": "首页","navigationStyle": "custom" // 使用自定义导航栏&#xff0c;系统会关…

LoadRunner从零开始之走近LoadRunner

3.1 LoadRunner 的运行原理 安装LoadRunner 后&#xff0c;在菜单“开始” 一“MercuryLoadRunner” 中&#xff0c;你会看 到这样一组程序&#xff0c;如图 3-1 所示。 • 其中Applications 下面的Analysis、Controller 和Virtual User Generator 是我们 做性能测试最常用的…

【基础配置】Python2/Python3并存安装配置教程

Nx01 产品简介 Python是一种高级的、解释型的、面向对象的通用编程语言&#xff0c;具有简单易学、代码可读性强、功能强大、可移植性好等特点。它可以应用于多种领域&#xff0c;如Web开发、数据科学、人工智能、机器学习、科学计算、自动化测试等。Python由Guido van Rossum于…

08章【文件与IO】

文章目录 File类IO流字节流字符流字节字符转换流缓冲流打印流对象流字节数组流数据流字符串流、管道流、合并流 RandomAccessFileProperties文件操作文件压缩与解压缩装饰者模式常见字符编码New IO File类 File类的基本概念 File类&#xff1a;表示文件和目录路径名的抽象表示…

【AIGC】CLIP

CLIP的基本原理 对比学习&#xff1a; Clip使用对比学习来训练模型。对比学习的目标是通过将正样本&#xff08;相似的图像和文本对&#xff09;与负样本&#xff08;不相似的图像和文本对&#xff09;进行比较&#xff0c;从而使模型学会区分不同样本之间的差异。这有助于模型…