解决方案 | 基于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 %
相关文章:

解决方案 | 基于SFTP协议的文件传输断点续传Java实现方案
背景 因项目需要,我们服务每天都需要通过SFTP协议来对接上下游进行文件传输,但是对于一些大文件,在与第三方公司的服务器对接过程中很可能会因为网络问题或上下游服务器性能问题导致文件上传或者下载被中断,每次重试都需要重新对…...

web前端项目-动画特效【附源码】
文章目录 一:赛车游戏动画HTML源码:JS源码:CSS源码:(1)normalize.css(2)style.css 二:吉普车动画演示HTML源码:CSS源码:(1)…...

蓝桥杯备战——6.串口通讯
1.分析原理图 由上图我们可以看到串口1通过CH340接到了USB口上,通过串口1我们就能跟电脑进行数据交互。 另外需要注意的是STC15F是有两组高速串口的,而且可以切换端口。 2.配置串口 由于比赛时间紧,我们最好不要去现场查寄存器手册&#x…...

Redis为什么速度快:数据结构、存储及IO网络原理总结
Redis,作为内存数据结构存储的佼佼者,其高性能表现一直备受赞誉。那么,Redis究竟是如何实现这一点的呢?接下来,我们将更深入地探讨其背后的关键技术,并提供进一步的优化策略。 一、内存存储与数据结构设计…...

OSI七层模型 | TCP/IP模型 | 网络和操作系统的联系 | 网络通信的宏观流程
文章目录 1.OSI七层模型2.TCP/IP五层(或四层)模型3.网络通信的宏观流程3.1.同网段通信3.2.跨网段通信 1.OSI七层模型 在计算机通信诞生之初,不同的厂商都生产自己的设备,都有自己的网络通讯标准,导致了不同厂家之间各种协议不兼容࿰…...

Java集合总览
1.总览 Java中的集合分List、Set、Queue、Map 4种类型。 List:大多数实现元素可以为null,可重复,底层是数组或链表的结构,支持动态扩容 Set:大多数实现元素可以为null但只能是1个,不能重复, …...

C# 设置一个定时器函数
C#中,创建设置一个定时器,能够定时中断执行特定操作,可以用于发送心跳、正计时和倒计时等。 本文对C#的定时器简单封装一下,哎,以方便定时器的创建。 定义 using Timer System.Timers.Timer;class SetTimer {Timer …...
第十四届蓝桥杯省赛pythonB组题。 管道
5407. 管道 - AcWing题库 有一根长度为 len的横向的管道,该管道按照单位长度分为 len 段,每一段的中央有一个可开关的阀门和一个检测水流的传感器。 一开始管道是空的,位于 Li 的阀门会在 Si 时刻打开,并不断让水流入管道。…...

淘宝扭蛋机小程序:新时代的互动营销与娱乐体验
随着科技的快速发展,小程序已经成为人们日常生活中不可或缺的一部分。在众多的小程序中,淘宝扭蛋机小程序以其独特的互动性和趣味性,吸引了大量用户。本文将深入探讨淘宝扭蛋机小程序的特色、用户体验以及未来发展。 一、淘宝扭蛋机小程序的…...

深度强化学习(王树森)笔记02
深度强化学习(DRL) 本文是学习笔记,如有侵权,请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接:https://github.com/wangshusen/DRL 源代码链接:https://github.c…...

【分布式技术专题】「分布式技术架构」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
探索Tomcat技术架构设计模式的奥秘 Tomcat系统架构分析Tomcat 整体结构Tomcat总体结构图以 Service 作为“婚姻”1) Service 接口方法列表 2) StandardService 的类结构图方法列表 3) StandardService. SetContainer4) StandardService. addConnector 以 Server 为“居”1) Ser…...
常用的gpt-4 prompt words收集8
本文介绍我最近收集的一些好用的chatgpt-4的prompts,如果你也有好用的提示词可以互相交流一下。 1. I ran into some trouble on my way to work. 迟到原因 2. In my heart, the most delicious coffee is the Hawaii Dirty from Manner. Only the Nong series a…...

【GitHub项目推荐--开源2D 游戏引擎】【转载】
microStudio 是一个可在浏览器中运行的游戏引擎,它拥有一套精美、设计精良、全面的工具,可以非常轻松地帮助你创建 2D 游戏。 你可以在浏览器中访问 microStudio.dev 开始搭建你的游戏,当然你可以克隆现有项目或创建新游戏并开始编码&#x…...

鸿蒙APP的应用场景
鸿蒙APP可以用于多种场合和设备类型,这是鸿蒙系统的分布式能力和多终端适配的优势。以下是一些鸿蒙APP的应用场景,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.智能手机和平板电脑&am…...

goland课程管理(6)
项目目录结构如下图所示: core包下面: class.go package coreimport "github.com/gin-gonic/gin"func Class1(ctx *gin.Context) {}course.go package coreimport (. "cookie/database". "cookie/model""fmt"…...
04.Elasticsearch应用(四)
Elasticsearch应用(四) 1.什么是索引 索引是文档的容器,是一类文档的结合索引是一个逻辑命名空间,它映射到一个或多个主分片,并且可以具有零个或多个副本分片索引中数据分散在Shard上索引的Mapping定义文档字段的类型…...

Python之数据可视化(地图)
目录 一 基础地图应用 二 全国疫情图 一 数据准备 二 数据处理 二 湖北省疫情图 一 数据准备 二 数据处理 一 基础地图应用 导入map地图对象 from pyecharts.charts import Map map Map() 写入数据 data [("北京市",100),("上海市"…...

etcd技术解析:构建高可用分布式系统的利器
1. 引言 随着云原生技术的兴起,分布式系统的构建变得愈发重要。etcd作为一个高可用的分布式键值存储系统,在这个领域发挥着至关重要的作用。本文将深入探讨etcd的技术细节,以及如何利用它构建高可用的分布式系统。 2. etcd简介 etcd是一个开…...
Pillow图像处理:从零开始的奇妙之旅
图像处理,就像是一场神奇的冒险,让我们的照片变得更有趣、更生动。而在这个冒险的旅途中,Pillow就如同一位魔法师,为我们开启了无尽的可能性。无论你是刚刚踏入图像处理领域的小白,还是已经略有基础的程序员࿰…...
设计一个LRU(最近最少使用)缓存
约束和假设 我们正在缓存什么? 我们正在缓存Web Query的结果我们可以假设输入是有效的,还是需要对其验证? 假设输入是有效的我们可以假设它适应内存吗? 对 编码实现 class Node(object):def __init__(self, results):self.res…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...