用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.

出来的效果是这样的。

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

(完)

相关推荐
Java技术小馆3 分钟前
如何设计一个本地缓存
java·面试·架构
XuanXu1 小时前
Java AQS原理以及应用
java
风象南4 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端
mghio13 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室17 小时前
java日常开发笔记和开发问题记录
java
咖啡教室17 小时前
java练习项目记录笔记
java
鱼樱前端18 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea19 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea19 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq