Osmanthus

空想具現化


  • 首页
  • 归档
  • 分类
  • 标签
  • 关于
  •   

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo

JSch 的使用(SSH、SFTP)

发布于 2024-09-28 JSch  SSH 

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();  
}

 上一篇: Dell G15 5530 下 Windows 11 + Ubuntu 20.04 双系统安装 下一篇: Docker 安装(rpm 仓库、rpm 软件包、二进制文件、银河麒麟环境) 

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo