解决方案 | 基于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 {// 设置JSchJSch 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];//1Mint 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;}@Overridepublic void init(int op, String src, String dest, long max) {System.out.println("Starting transfer: " + src + " --> " + dest);}@Overridepublic 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;}@Overridepublic 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 {// 设置JSchJSch 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];//1Mint 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;}@Overridepublic void init(int op, String src, String dest, long max) {System.out.println("Starting transfer: " + src + " --> " + dest);}@Overridepublic 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;}@Overridepublic 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 % 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode.2865. 美丽塔 I

题目 题目链接 分析 闲谈&#xff1a;每次读 LeetCode 的题目描述都要费老大劲&#xff0c;o(╥﹏╥)o 题意&#xff1a;这个其实意思就是以数组的每一位作为最高点&#xff0c;这个点&#xff08;数字&#xff09;左右两边的数字都不能大于这个数字(可以等于)&#xff0c;…

前端JavaScript篇之找出数组中重复的数字、js中数组是如何在内存中存储的?原生遍历数组的方式有哪些?请对以下数组,根据 `born` 的值降序排列

目录 找出数组中重复的数字方法一&#xff1a;使用 Set 数据结构方法二&#xff1a;排序数组 js中数组是如何在内存中存储的&#xff1f;原生遍历数组的方式有哪些&#xff1f;请对以下数组&#xff0c;根据 born 的值降序排列 找出数组中重复的数字 找出数组中的重复数字是一…

【赠书第18期】人工智能B2B落地实战:基于云和Python的商用解决方案

文章目录 前言 1 方案概述 2 方案实施 2.1 云平台选择 2.2 Python环境搭建 2.3 应用开发与部署 2.4 应用管理 2.5 安全性与隐私保护 3 方案优势与效益 4 推荐图书 5 粉丝福利 前言 随着云计算技术的快速发展&#xff0c;越来越多的企业开始将业务迁移至云端&#x…

云原生离线工作流编排利器 -- 分布式工作流 Argo 集群

作者&#xff1a;庄宇 在现代的软件开发和数据处理领域&#xff0c;批处理作业&#xff08;Batch&#xff09;扮演着重要的角色。它们通常用于数据处理&#xff0c;仿真计算&#xff0c;科学计算等领域&#xff0c;往往需要大规模的计算资源。随着云计算的兴起&#xff0c;阿里…

鸿蒙开发入门

下载和安装DevEco Studio&#xff1a; 文档中心https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/software_install-0000001053582415-V3 配置鸿蒙开发环境&#xff1a; 文档中心https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3…

MySQL之数据库DDL

文章目录 MySQL数据库基本操作数据定义DDL对数据库的常用操作创建表修改表格式结构 MySQL数据库基本操作 首先我们先了解SQL的语言组成&#xff0c;他分为四个部分 数据定义语言&#xff08;DDL&#xff09;数据操纵语言&#xff08;DML&#xff09;数据控制语言&#xff08;…

亚马逊、eBay、TikTok等平台的综合运营实用工具分享!

亚马逊、eBay等电商平台为卖家提供了广阔的销售机会&#xff0c;但同时也带来了运营管理的挑战。为了提高运营效率和销售业绩&#xff0c;卖家需要借助一些实用工具。本文将介绍一些在亚马逊、eBay等平台上综合运营中非常有用的工具&#xff0c;帮助卖家更高效地管理店铺&#…

CC工具箱使用指南:【处理面要素空洞】

一、简介 面要素在经过一系列的处理后&#xff0c;可能会存在空洞。 有些小空洞面积过小&#xff0c;人工检查很容易遗漏&#xff0c;于是就做了这个工具。 目的就是获取面要素的空洞&#xff0c;或者去除空洞获取要素的边界。 二、工具参数介绍 右键点击面要素图层&#xf…

spring mvc 请求与响应

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

神经网络的学习(Neural Networks: Learning)

1.代价函数 案例&#xff1a;假设神经网络的训练样本有&#x1d45a;个&#xff0c;每个包含一组输入&#x1d465;和一组输出信号&#x1d466;&#xff0c;&#x1d43f;表示神经网络层数&#xff0c;&#x1d446;&#x1d43c;表示每层的 neuron 个数(&#x1d446;&#…

k8s 容器 java 应用内存限制不生效

一 k8s java 应用内存限制不生效 回顾&#xff1a;Linux杂谈之java命令 namespace负责资源隔离 cgroups负责资源限制 容器JVM最佳实践 Metaspace 是 非 Heap 内存 管理空间,那么 Heap 就是操作空间 JVM内存模型简介 隔离&#xff1a; 两个进程完全隔离感知&#xff1…

VM虚拟机忘记密码,ISO镜像修改

VM虚拟机忘记密码&#xff0c;ISO镜像修改 制作镜像&#xff1a; 镜像已制作&#xff0c;可在文末链接自行获取从镜像启动系统 1&#xff09;添加IOS镜像文件&#xff1a; 2&#xff09;开机进去固件&#xff1a; 进入后选择对应的驱动器启动 3. 修改密码 点击修改密码软件&a…

【极数系列】Flink环境搭建(02)

【极数系列】Flink环境搭建&#xff08;02&#xff09; 引言 1.linux 直接在linux上使用jdk11flink1.18.0版本部署 2.docker 使用容器部署比较方便&#xff0c;一键启动停止&#xff0c;方便参数调整 3.windows 搭建Flink 1.18.0版本需要使用Cygwin或wsl工具模拟unix环境…

每日一题——LeetCode1309.解码字母到整数映射

方法一 个人方法&#xff1a; 由题意可得&#xff1a; 1、1-26对应a-z&#xff0c;只不过从10开始&#xff0c;后面要加#才能表示字母 2、10# - 26# 都是三位数&#xff0c;开头<2&#xff0c;结尾都为# 3、某个数后面两位如果不为#那么该数为1-9对应a-i&#xff0c;如果…

华为设备配置简单流分类

实验需求&#xff1a;10.1.1.0/24网段的PC访问PC3&#xff0c;流量进入AR1时&#xff0c;DSCP字段优先级为0&#xff0c;在AR1进行配置&#xff0c;将10.1.1.0/24网段流量的优先级映射为内部优先级46。 实验步骤&#xff1a; 步骤1&#xff1a;配置IP地址 [AR1]interface g0/…

Pygame之纯Python实现你好2024效果

Pygame之纯Python实现你好2024效果 前言&#xff1a; 对于某些指JavaScript与前端实现为Python实现你好2024效果的营销号实在看不下去了。无底线营销&#xff0c;还要私信拿源码&#xff0c;hhh 于是就有了以下代码&#xff1a; 运行前安装pygame pip install pygame运行效果…

k8s-基础知识(Service,NodePort,CusterIP,NameSpace,资源限制)

Node Node 是 Pod 真正运行的主机&#xff0c;可以是物理机&#xff0c;也可以是虚拟机。 Annotations 原文链接 Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象&#xff0c;Annotations 则是用来记录一些附加信息&#xff0c;用来辅助应…

HTML 入门手册(一)

目录 HTML介绍 1-基础语法 单标签 双标签 整体结构 2-标题和水平线 标题 水平线 3-段落和换行 段落 换行 4-列表 无序列表 有序列表 嵌套列表 5-div和span div span 6-格式化标签 粗体 斜体 下划线中划线 上标和下标 7-超链接(a标签) 链接到URL 链接…

GitHub无法完成推送 的设置选项

GitHub无法完成推送 的设置选项 系统设置 VS中控制台设置【指令】 控制台调出方法 以下为VS控制台指令 git config --global --unset http.proxy git config --global --unset https.proxygit config --global http.proxy 127.0.0.1:7890 git config --global https.proxy …

docker - compose 部署 Tomcat

目录 下面用 docker-compose 方法部署 Tomcat 1、准备工作 2、部署容器 启动容器 查看新启动的容器 3、总结 下面用 docker-compose 方法部署 Tomcat 1、准备工作 先在主机创建工作文件夹&#xff0c;为了放置 Tomcat 的配置文件等。创建文件夹的方法&#xff0c;自己搞…