sftp与ftp
要谈sftp
(SSH File Transfer Protocol
),首先要谈ftp
(File Transfer Protocol
),大家都知道ftp是文件传输协议,它基于tcp
协议,可以用来发送文件。
sftp与ssh
那sftp
,就是安全(security)的ftp
,因为它是基于ssh
协议,ssh 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题
,ssh
在连接和传送的过程中会加密所有的数据。所以通俗的来讲,通过ssh协议进行文件传输,那就是sftp。
那么如何使用ssh
来实现文件传输呢?熟悉linux的伙伴们应该对ssh
也不陌生,因为linux自带了ssh(嘘!其实我并不熟悉linux,只是知道linux自带了ssh,以前装了没多久就卸了,现在发现会linux逼格会提高不少。一定要再装一个玩一下!)。
遗憾的是,ssh基本上是基于linux和一些客户端安装软件。那么在我们平常的web开发中,要用sftp来传输文件怎么办呢?jsch就是解决办法了。
jsch简介
jsch
是ssh的纯java实现。这么讲有点抽象,通俗说,你在官网上down下来就是一个jar包,引入你的项目,就可以开始使用
- 第一步:首先在maven*仓库中查一下怎么在pom中依赖,可以点这里。
-
<!-- https://mvnrepository.com/artifact/com.jcraft/jsch --> <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency>
- 第二步:创建一个工具类:SFTPClient.java, 实现文件上传、下载、读取等功能:代买如下:
-
package com.zjh.logviewer.ssh; import com.zjh.logviewer.model.FileAttri; import com.zjh.logviewer.model.Server; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Vector; import org.apache.commons.lang3.ArrayUtils; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; public class SFTPClient extends BaseJchClient { private ChannelSftp channel; public SFTPClient(Server serverInfo) throws RemoteAccessException { super(serverInfo); } private ChannelSftp openChannel() throws RemoteAccessException { try { if (channel != null) { if (channel.isConnected() || !channel.isClosed()) { channel.disconnect(); channel = null; } else { return channel; } } conn(); channel = (ChannelSftp) session.openChannel("sftp"); channel.connect(DEFAULT_CONN_TIMEOUT); return channel; } catch (JSchException e) { throw new RemoteAccessException(e); } } /** * 从sftp服务器下载指定文件到本地指定目录 * * @param remoteFile 文件的绝对路径+fileName * @param localPath 本地临时文件路径 * @return */ public boolean download(String remoteFile, String localPath) throws RemoteAccessException { ChannelSftp sftp = null; try { sftp = openChannel(); sftp.get(remoteFile, localPath); return true; } catch (SftpException e) { logger.error("download remoteFile:{},localPath:{}, ex:{}", remoteFile, localPath, e); throw new RemoteAccessException(e); } } /** * 上传文件 * * @param directory 上传的目录-相对于SFPT设置的用户访问目录, 为空则在SFTP设置的根目录进行创建文件(除设置了服务器全磁盘访问) * @param uploadFile 要上传的文件全路径 */ public boolean upload(String directory, String uploadFile) throws Exception { ChannelSftp sftp = null; try { try { sftp = openChannel(); sftp.cd(directory); // 进入目录 } catch (SftpException sException) { if (sftp.SSH_FX_NO_SUCH_FILE == sException.id) { // 指定上传路径不存在 sftp.mkdir(directory);// 创建目录 sftp.cd(directory); // 进入目录 } } File file = new File(uploadFile); InputStream in = new FileInputStream(file); sftp.put(in, file.getName()); in.close(); } catch (Exception e) { throw new Exception(e.getMessage(), e); } return true; } /** * 读取sftp上指定文件数据 * * @param remoteFile * @return */ public byte[] getFile(String remoteFile) throws RemoteAccessException { ChannelSftp sftp = null; InputStream inputStream = null; try { sftp = openChannel(); inputStream = sftp.get(remoteFile); return IOHelper.readBytes(inputStream); } catch (SftpException | IOException e) { logger.error("getFile remoteFile:{},ex:{}", remoteFile, e); throw new RemoteAccessException(e); } finally { IOHelper.closeQuietly(inputStream); } } /** * 读取sftp上指定(文本)文件数据,并按行返回数据集合 * * @param remoteFile * @param charset * @return */ public String getFileContent(String remoteFile, Charset charset) throws RemoteAccessException { ChannelSftp sftp = null; InputStream inputStream = null; try { sftp = openChannel(); inputStream = sftp.get(remoteFile); return IOHelper.readText(inputStream, charset); } catch (SftpException | IOException e) { logger.error("getFileText remoteFile:{},error:{}", remoteFile, e); throw new RemoteAccessException(e); } finally { IOHelper.closeQuietly(inputStream); } } /** * 列出指定目录下文件列表 * * @param remotePath * @param descendant 是否递归查询子孙目录 * @param excludes 要排除的文件 * @return */ public List<FileAttri> ls(String remotePath, boolean descendant, String... excludes) throws RemoteAccessException { ChannelSftp sftp = null; List<FileAttri> lsFiles; try { sftp = openChannel(); lsFiles = ls(sftp, remotePath, descendant, excludes); } catch (SftpException e) { logger.error("ls remotePath:{} , error:{}", remotePath, e.getMessage()); if ("Permission denied".equals(e.getMessage())) { throw new PermissionException("没有文件读取权限"); } throw new RemoteAccessException(e); } if (lsFiles != null) { Collections.sort(lsFiles); } return lsFiles; } @SuppressWarnings("unchecked") private List<FileAttri> ls(ChannelSftp sftp, String remotePath, boolean descendant, String... excludes) throws SftpException { List<FileAttri> lsFiles = new ArrayList<>(); FileAttri tmpFileAttri; long tmpSize; String tmpFullPath; Vector<LsEntry> vector = sftp.ls(remotePath); for (LsEntry entry : vector) { if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) { continue; } tmpFullPath = UrlHelper.mergeUrl(remotePath, entry.getFilename()); if (excludes != null && ArrayUtils.contains(excludes, tmpFullPath)) { logger.debug("忽略目录:{}", tmpFullPath); continue; } tmpFileAttri = new FileAttri(); tmpFileAttri.setNodeName(entry.getFilename()); tmpFileAttri.setLastUpdateDate(entry.getAttrs() .getATime()); tmpFileAttri.setPath(tmpFullPath); if (entry.getAttrs() .isDir()) { tmpFileAttri.setDir(true); if (descendant) { try { List<FileAttri> childs = ls(sftp, tmpFileAttri.getPath(), descendant, excludes); if (CollectionHelper.isNotEmpty(childs)) { tmpFileAttri.addNodes(childs); } } catch (PermissionException e) { tmpFileAttri.setNodeName(tmpFileAttri.getNodeName() + "[无权限]"); } } } else { tmpFileAttri.setDir(false); tmpSize = entry.getAttrs() .getSize(); if (tmpSize < 1024) { tmpFileAttri.setSize(entry.getAttrs() .getSize() + "B"); } else if (tmpSize >= 1024 && tmpSize < 1048576) { tmpFileAttri.setSize(MathHelper.round((entry.getAttrs() .getSize() / 1024f), 1) + "KB"); } else if (tmpSize > 1048576) { tmpFileAttri.setSize(MathHelper.round((entry.getAttrs() .getSize() / 1048576f), 2) + "MB"); } } lsFiles.add(tmpFileAttri); } return lsFiles; } /** * 判断文件是否存在 * * @param filePath * @return * @throws RemoteAccessException */ public boolean isExist(String filePath) throws RemoteAccessException { ChannelSftp sftp = null; try { sftp = openChannel(); SftpATTRS attrs = sftp.lstat(filePath); return attrs != null; } catch (SftpException e) { logger.error("文件不存在,remotePath:{} , error:{}", filePath, e.getMessage()); return false; } } @Override public void close() throws IOException { if (channel != null) { try { channel.disconnect(); } catch (Exception e) { channel = null; } } super.close(); } } 点击并拖拽以移动
第三步:创建ssh交互的工具类:SSHClient.java, 实现执行单条命令、异步执行命令的功能:代码如下:huo获取
-
import com.zjh.logviewer.model.Server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSchException; public class SSHClient extends BaseJchClient { public SSHClient(Server serverInfo) throws RemoteAccessException { super(serverInfo); } /** * 执行单条命令 * * @param cmd * @return StringBuffer命令结果 * @throws RemoteAccessException */ public StringBuffer exec(String cmd) throws RemoteAccessException { String line = null; BufferedReader reader = null; ChannelExec channelExec = null; StringBuffer resultBuf = new StringBuffer(); InputStream inStream = null; try { channelExec = getChannel(cmd); stopLastCmdThread();// 中断前一个异步命令的执行 inStream = channelExec.getInputStream(); channelExec.connect(); if (inStream != null) { reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name()); while ((line = reader.readLine()) != null) { resultBuf.append(line) .append(Chars.LF); } } } catch (IOException | JSchException e) { logger.error("执行命令异常,ip:{},cmd:{},ex:{}", serverInfo.getIp(), cmd, e); throw new RemoteAccessException(e); } finally { IOHelper.closeQuietly(inStream); if (channelExec != null) { channelExec.disconnect(); } } if (logger.isDebugEnabled()) { logger.debug("{}执行命令:{},结果:{}", serverInfo.getIp(), cmd, resultBuf); } return resultBuf; } ExecAsyncCmdThread cmdThread = null; /** * 异步执行命令 * * @param cmd * @param handler * @throws RemoteAccessException */ public void execAsync(final String cmd, final AsyncCmdCallBack callBack) throws RemoteAccessException { ChannelExec channelExec = getChannel(cmd); stopLastCmdThread(); cmdThread = new ExecAsyncCmdThread(channelExec, callBack); cmdThread.setDaemon(true); cmdThread.start(); } private ChannelExec getChannel(String cmd) throws RemoteAccessException { conn(); try { ChannelExec channel = (ChannelExec) session.openChannel("exec"); channel.setCommand(cmd); channel.setInputStream(null); channel.setErrStream(System.err); return channel; } catch (JSchException e) { throw new RemoteAccessException(e); } } class ExecAsyncCmdThread extends Thread { private AsyncCmdCallBack callBack; private ChannelExec channelExec = null; private volatile boolean isRunning = false; public ExecAsyncCmdThread(final ChannelExec channelExec, AsyncCmdCallBack callBack) { this.channelExec = channelExec; this.callBack = callBack; } @Override public void run() { InputStream inStream = null; String line; BufferedReader reader = null; channelExec.setPty(true); try { inStream = channelExec.getInputStream(); channelExec.connect(); isRunning = true; reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name()); while (isRunning) { while ((line = reader.readLine()) != null) { callBack.hanndle(line); } if (channelExec.isClosed()) { int res = channelExec.getExitStatus(); isRunning = false; System.out.println(String.format("Exit-status: %d,thread:%s", res, Thread.currentThread() .getId())); break; } } } catch (Exception e) { logger.error("", e); } finally { IOHelper.closeQuietly(reader); if (channelExec != null) { channelExec.disconnect(); } isRunning = false; } } public void close() { channelExec.disconnect(); isRunning = false; } } void stopLastCmdThread() { if (cmdThread != null) { cmdThread.close(); } } @Override public void close() throws IOException { stopLastCmdThread(); cmdThread = null; super.close(); } }
第四步:在需要使用的地方,调用此两个类即可
获取项目源码
https://github.com/BigDataAiZq/logviewer