用JSch实现远程传输文件并打包成jar

本文将简单介绍一下 JSch 这个Java的第三方库的一个简单用法,并以此为实例,讲解 IntelliJ 中打包成 jar 包的2种方式。

实现目标

我们的目标是,做出一个jar包,它能够实现类似于 scp 命令的远程传输文件的功能。用法如下:

java -jar UseJsch.jar scp /home/finix/1.txt 192.168.10.21:/home/finix/1.txt 

即,将本地的文件 /home/finix/1.txt 拷贝到远程机器 192.168.10.21 的 /home/finix/1.txt

简介 JSch

JSch 的官网是:http://www.jcraft.com/jsch/

JSch 是SSH2的纯Java实现。 它拥有诸多的和ssh相关的功能,比如 ssh 执行命令、scp等等,并被使用在一些著名的开源软件中。这个库的最新版本似乎就是 0.1.55, 于2018年左右就停止更新了。

本文不打算深入罗列JSch的各种功能,只是使用了其ChannelSftp类的put功能用于远程传输文件。现将ChannelSftp类的一些关键方法罗列如下:

  • put文件上传
  • get文件下载
  • cd进入指定目录
  • ls取得指定目录下的文件列表
  • rename重命名指定的文件或目录
  • mkdir创建目录
  • rmdir删除目录
  • rm删除指定文件

JSch的maven地址

<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>

Jsch的gradle写法(build.gradle)

dependencies {  
    'com.jcraft:jsch:0.1.55'  
}

核心代码

要实现scp文件的功能,可以使用下面的这个类。先看代码,其中具体使用已经写了注释,就不在这里介绍了。

java 复制代码
package com.use_jsch;

import com.jcraft.jsch.*;

import java.io.*;
import java.util.Properties;

public class UseJsch {
    public static void scpFile(int servicePort, String srcFilePath, String dstFilePath, String dstAddress) throws IOException {
        final String USER = "finix";
        final String PASSWORD = "123456789";
        final String PRIVATE_KEY_FILE = "/home/finix/.ssh/id_rsa";
        final String SESSION_TYPE = "sftp";
        final int SSH_TIMEOUT = 30000;  // 30 seconds
        final int SSH_COMMAND_ACK_TIMEOUT = 5000;   // 5 seconds
        final int CHANNEL_TIMEOUT = 5000;   // 5 seconds

        File file = new File(srcFilePath);
        if (!file.exists()) {
            throw new RuntimeException(String.format("%s does not exist", srcFilePath));
        }

        Session session = null;
        Channel channel = null;
        ChannelSftp sftp = null;
        FileInputStream fileInputStream = null;

        try {
            JSch jsch = new JSch();
            // jsch.addIdentity(PRIVATE_KEY_FILE);
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("PreferredAuthentications", "publickey,password");
            session = jsch.getSession(USER, dstAddress, servicePort);
            session.setPassword(PASSWORD);
            session.setConfig(config);

            System.out.println("SCP_FILE: 1. session connecting...");
            session.connect(SSH_TIMEOUT);
            session.setTimeout(SSH_COMMAND_ACK_TIMEOUT);

            System.out.println("SCP_FILE: 2. open channel and connect...");
            channel = session.openChannel(SESSION_TYPE);
            channel.connect(CHANNEL_TIMEOUT);
            sftp = (ChannelSftp) channel;

            System.out.println("SCP_FILE: 3. scp file...");
            fileInputStream = new FileInputStream(file);
            sftp.setInputStream(fileInputStream);
            sftp.put(fileInputStream, dstFilePath);
            System.out.println("SCP_FILE: END");
        } catch (JSchException e) {
            System.err.println("SCP_FILE: JSchException: " + e);
            throw new RuntimeException("SCP_FILE: JSchException: " + e);
        } catch (IOException e) {
            System.err.println("SCP_FILE: IOException: " + e);
            throw new RuntimeException("SCP_FILE: IOException: " + e);
        } catch (Exception e) {
            System.err.println("SCP_FILE: ERROR: " + e);
            throw new RuntimeException("SCP_FILE: ERROR: " + e);
        } finally {
            if (fileInputStream != null) fileInputStream.close();
            if (channel != null) channel.disconnect();
            if (session != null) session.disconnect();
        }
    }

    public static void main(String[] args) {
        if (args.length != 3 || !args[0].equals("scp")) {
            System.out.println("Usage: java -jar UseJsch.jar scp <src_file_path> <ip>:<dest_file_path>");
            System.exit(1);
        }

        String srcFilePath = args[1];
        String[] destFileAndDestAddress = args[2].split(":");
        String dstFilePath = destFileAndDestAddress[1];
        String dstAddress = destFileAndDestAddress[0];
        try {
            System.out.printf("Start to scp file from %s to %s:%s%n", srcFilePath, dstAddress, dstFilePath);
            scpFile(22,srcFilePath, dstFilePath, dstAddress);
        }
        catch (Exception ex) {
            System.err.println("SCP FILE ERROR: " + ex);
        }
    }
}

这个java文件如果要直接测的话,可以将第一行 "package xxx" 删除,然后运行

shell 复制代码
java -cp lib/jsch-0.1.55.jar UseJsch.java /home/finix/1.txt 192.168.10.21:/home/finix/1.txt

运行效果就是:

Start to scp file from /home/finix/1.txt to 192.168.10.21:/home/finix/1.txt
SCP_FILE: 1. session connecting...
SCP_FILE: 2. open channel and connect...
SCP_FILE: 3. scp file...
SCP_FILE: END

值得一提的有2个地方:

  1. config.put("StrictHostKeyChecking", "no");
    这句话相当于 ssh 或 scp 的时候用的命令行参数 -o StrictHostKeyChecking=no, 其作用是,令其不再出现交互式的提问

    The authenticity of host '192.168.10.21 (192.168.10.21)' can't be established.
    ECDSA key fingerprint is SHA256:lSbwytjbqqvCC2FILkMb+M+yaeUTxEYPAPFTVIjTFOg.
    Are you sure you want to continue connecting (yes/no/[fingerprint])?

这是因为这个目标IP没有被加到~/.ssh/known_hosts文件中,所以询问你是否要继续连接。我们用了上面的选项-o StrictHostKeyChecking=no后,就不会再交互式提问,而是默认yes.

  1. config.put("PreferredAuthentications", "publickey,password");
    这一行代表优先使用的认证方式是publickeypassword这两种。
    (据说JSch提供了4种认证方式,但比较常用的应该还是以上提到的这2种)
    这一句的意思是优先使用publickey, 如果不行了再使用password. 如果要使用publickey, 那么其实还要再加一句
java 复制代码
final String PRIVATE_KEY_FILE = "/home/finix/.ssh/id_rsa";
...
jsch.addIdentity(PRIVATE_KEY_FILE);

而在本文程序中, jsch.addIdentity(PRIVATE_KEY_FILE);被注释掉了,所以本文程序仍旧使用的是密码的方式。

程序部分就介绍到这里。下面是打包的流程。

打包为jar包

Step 1. 首先,在IntelliJ中新建一个gradle的项目,并加入源代码UseJsch.java, 而build.gradle中依赖包的加法已经在上文中描述过了,就不再重复。这里的目录结构大约是这样的:

Step 2. File -> Project Structure -> Module, 此处鼠标单击选中 "UseJsch" (不要选中其下面的"main"),此处应该能看到 "Gradle: com.jcraft:jsch:0.1.55";若看不到,则此处需添加之。
这个添加的动作很重要:
1> 选中 "Module Source"
2> 点击加号 "+"
3> 弹出框中单击 Library
4> 继续的弹出框中选中 "Gradle: com.jcraft:jsch:0.1.55" , 点击 Add Selected
5> 最后点击OK.


Step 3. 还是在上面的界面,鼠标单击选中 "Artifacts", 再点击"+" 和 "From modules with dependencies"

Step 4. 在弹出框中填"Module"、"Main class"、"JAR files from libraries"

注意,这里的 "JAR files from libraries" 有2个选项

  • extract to the target JAR
    这个选项的含义是:将第三方库的jar文件解压成class文件,然后拷贝到和主类相同的目录结构中。
    如果用了这个选项,我们就不用设置classpath来寻找第三方的库了。
  • copy to the output directory and link via manifest
    这个选项的含义是:第三方库将仍以jar包的形式存在,它会被拷贝到指定的地方(如lib目录);主类自己会被打一个jar包;因此需要在给主类打包的时候,指定MANIFEST文件中的classpath怎么写(见下文"打包方式-2")

打包方式-1: extract to the target JAR

Step 5.

上文Step-4中点击OK后,我们可以看到如下的界面:

注意,这里没有主类 UseJsch, 所以还得点击加号, 以添加主类。添加之后的效果如下:

注意,此处不用填写Class Path,因为第三方库已经被解压并和主类放置到了相同的目录结构下。

现在点击OK.

Step 6. Build -> Build Artifacts

此处点击 Build 或 Rebuild.

Build结束之后,我们在 output/artifacts/UseJsch_jar 目录下可以看到 UseJsch.jar 文件了!

右键选winrar打开,我们可以看到 jcraft 库的代码已经被解压和use_jsch包放到了一起:

最后,可以运行这个UseJsch.jar了:

shell 复制代码
$ java -jar UseJsch.jar scp /home/finix/1.txt 192.168.10.21:/home/finix/1.txt
Start to scp file from /home/finix/1.txt to 192.168.10.21:/home/finix/1.txt
SCP_FILE: 1. session connecting...
SCP_FILE: 2. open channel and connect...
SCP_FILE: 3. scp file...
SCP_FILE: END

在上文Step-4的时候选中 "copy to the output directory and link via manifest", 点OK

然后可以看到如下的界面。

注意,

这里我们已经可以看到第三方库 jsch-0.1.55.jar了,这是我们在Step-2所作的工作的效果;

(如果没有Step-2的工作,这里将不会显示第三方库,因此就需要手动添加:点击加号"+",再点击 Library Files)。

但是,此时并没有 主类,所以我们需要手动加主类添加上去:**单击选中"UseJsch.jar", 点击加号,再点击"Module Output"*, 如下:

选中主类,最后点击OK, 效果见下一步的图。

Step-5. 点击选中"UseJsch.jar", 然后在下面出现的框中填写 "Manifest File"、"Main Class"、和"Class Path"

最后,剩下来的步骤和打包方式-1相同,就是Build -> Build Artifacts.

出来的效果是这样的。

运行效果也是一样,不再赘述。

(完)

相关推荐
九圣残炎27 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge29 分钟前
Netty篇(入门编程)
java·linux·服务器
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航1 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself2 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq04152 小时前
J2EE平台
java·java-ee
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee