JSch 概要
[!info]
首先明确现在应当使用的是com.github.mwiede:jsch
,而不是com.jcraft:jsch
注意所需的最低 Java 版本为 Java 8
项目地址:
https://github.com/mwiede/jsch
示例代码:
https://github.com/mwiede/jsch/tree/master/examples
如这一篇博文中解释的一样,应当替换的主要原因有:
- OpenSSH 在 8.8 中默认禁用了
ssh-rsa
,因此需要一个支持rsa-sha2-256
和rsa-sha2-512
的库。 - 替换方便:只需更改依赖关系坐标即可。
- JCraft 发布的 JSch 在 2018 年后就停止了维护。
- 与 OpenJDK 功能保持同步,因此无需额外依赖。
将坐标
<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
替换为
<!-- https://mvnrepository.com/artifact/com.github.mwiede/jsch -->
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>0.2.19</version>
</dependency>
即可。
建立连接
使用用户名、密码登录:
JSch jsch = new JSch();
Session session = jsch.getSession(USER, HOST, PORT);
session.setPassword(PASSWORD);
// yes / no / ask
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
使用密钥登录,注意需要先在客户端和服务端完成配置:
JSch jsch = new JSch();
// Windows 环境为例
jsch.addIdentity("C:\\Users\\{user}\\.ssh\\id_rsa");
sch.setKnownHosts("C:\\Users\\{user}\\.ssh\\known_hosts");
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
建立连接后即可进行文件上传、下载,远程命令执行等操作,最后断开连接:
session.connect();
// Do something
// ...
session.disconnect();
遍历文件
通过 ChannelSftp 完成相关操作,使用后断开连接。
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
walk(channelSftp, "/etc/ssh");
channelSftp.disconnect();
[!note]
递归遍历,注意需要排除.
与..
private static void walk(ChannelSftp channelSftp, String path) throws SftpException {
Vector<ChannelSftp.LsEntry> vector = channelSftp.ls(path);
for (ChannelSftp.LsEntry entry : vector) {
SftpATTRS attrs = entry.getAttrs();
// System.out.println("attrs: " + attrs);
// System.out.println("longname: " + entry.getLongname());
String filename = entry.getFilename();
if (attrs.isDir()) {
if (filename.equals(".") || filename.equals("..")) {
continue;
}
System.out.println("directory: " + filename);
walk(channelSftp, path + "/" + filename);
} else {
System.out.println("file: " + path + "/" + filename);
}
}
}
删除文件
同上,通过 ChannelSftp 完成相关操作,使用后断开连接。
[!note]
注意ChannelSftp.rm(path)
不可以删除非空目录。
如果目录非空,需要先递归删除目录中文件。
private static void delete(ChannelSftp channelSftp, String remotePath) throws SftpException {
// stat: 链接所指向的文件的信息,而不是链接本身的信息
// lstat: 链接本身的状态信息,而不是链接指向的文件的信息
// SftpATTRS stat = channelSftp.stat(remotePath);
SftpATTRS lstat = channelSftp.lstat(remotePath);
if (lstat.isReg()) {
channelSftp.rm(remotePath);
return;
}
// lstat.isDir()
Vector<ChannelSftp.LsEntry> vector = channelSftp.ls(remotePath);
for (ChannelSftp.LsEntry entry : vector) {
String filename = entry.getFilename();
String path = remotePath + "/" + filename;
if (entry.getAttrs().isReg()) {
channelSftp.rm(path);
} else {
if (filename.equals(".") || filename.equals("..")) {
continue;
}
delete(channelSftp, path);
}
}
channelSftp.rmdir(remotePath);
}
上传文件
同上,通过 ChannelSftp 完成相关操作,使用后断开连接。
[!note]
上传ChannelSftp.put()
具有多个重载方法
上传支持三种传输模式:ChannelSftp.OVERWRITE
、ChannelSftp.RESUME
、ChannelSftp.APPEND
默认使用的模式为ChannelSftp.OVERWRITE
private static void upload(ChannelSftp channelSftp, String localPath, String remotePath) throws SftpException {
// channelSftp.put(localPath, remotePath);
BufferedInputStream inputStream = FileUtil.getInputStream(localPath);
// 等价 channelSftp.put(inputStream, remotePath, ChannelSftp.OVERWRITE);
channelSftp.put(inputStream, remotePath);
}
通过实现接口 com.jcraft.jsch.SftpProgressMonitor
来监控传输进度。
private static void upload(ChannelSftp channelSftp, String localPath, String remotePath) throws SftpException {
// 传输监控
SftpProgressMonitor monitor = new SftpProgressMonitor() {
private long transferred;
@Override
public void init(int op, String src, String dest, long max) {
System.out.println("传输开始");
}
@Override
public boolean count(long count) {
transferred = transferred + count;
// System.out.println("当前传输的总大小: " + transferred + " bytes");
System.out.println("当前传输的总大小: " + transferred / (1024 * 1024) + " MB");
return true;
}
@Override
public void end() {
System.out.println("传输完成");
}
};
BufferedInputStream inputStream = FileUtil.getInputStream(localPath);
// 等价 channelSftp.put(inputStream, remotePath, monitor, ChannelSftp.OVERWRITE);
channelSftp.put(inputStream, remotePath, monitor);
}
下载文件
同上,通过 ChannelSftp 完成相关操作,使用后断开连接。
[!note]
下载ChannelSftp.get()
具有多个重载方法
下载同样支持通过 SftpProgressMonitor 来监控传输进度
private static void download(ChannelSftp channelSftp, String remotePath, String localPath) throws SftpException {
// channelSftp.get(remotePath, localPath);
InputStream inputStream = channelSftp.get(remotePath);
FileUtil.writeFromStream(inputStream, localPath);
}
其他支持的常规功能
// 远程
System.out.println("home: " + channelSftp.getHome());
System.out.println("pwd: " + channelSftp.pwd());
// 本地
System.out.println("local home: " + channelSftp.lpwd());
// mkdir
channelSftp.mkdir("/home/test");
// cd
channelSftp.cd("/home/test");
System.out.println("pwd: " + channelSftp.pwd());
// rename
channelSftp.rename("/home/test", "/home/test2");
System.out.println("test2 lstat: " + channelSftp.lstat("/home/test2"));
// rmdir
// 注意同样不可以删除非空目录
channelSftp.rmdir("/home/test2");
执行命令
JSch 中,ChannelShell 用于交互式 shell,ChannelExec 用于单次命令执行,这里使用 ChannelExec。
[!note]
执行命令的返回信息通过channelExec.getInputStream()
读取。
执行命令的错误信息通过设置channelExec.setErrStream(OutputStream)
接收。
private static void execCmd(Session session, String command) throws JSchException, IOException {
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
channelExec.setCommand(command);
// 设置错误输出流
// channelExec.setErrStream(System.err);
ByteArrayOutputStream errorOutputStream = new ByteArrayOutputStream();
channelExec.setErrStream(errorOutputStream);
// 获取输入流以读取命令输出
InputStream in = channelExec.getInputStream();
// 执行命令
channelExec.connect();
// 读取命令的输出
// BufferedReader reader = IoUtil.getReader(in, StandardCharsets.UTF_8);
/*BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();*/
// 使用 cn.hutool.core.io.IoUtil 简化
String output = IoUtil.read(in, StandardCharsets.UTF_8);
if (!output.isEmpty()) {
System.out.println("Exec Output: " + command + "\r\n" + output);
}
// 处理错误流
String errorMsg = errorOutputStream.toString();
if (!errorMsg.isEmpty()) {
System.err.println("Error Output: " + errorMsg);
}
channelExec.disconnect();
}