前言

前段时间,接了个需求,需要在项目中ssh连接远程服务器,并执行一个脚本。那最重要的就是要找到一个可以在Java程序支持SSH的工具,然后就找到了JSch 。还有一个是SSH2,不过我连接没成功。


JSch

本文只是JSch的一些简单用法,想了解更多的,可以去官网:
链接: http://www.jcraft.com/jsch/.

以下是JSch 官方的介绍
JSch 是SSH2的一个纯Java实现。它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等。你可以将它的功能集成到你自己的 Java程序中。JSch使用BSD风格许可证。

下面,我们就来使用JSch

1、添加依赖

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

2、JSch 连接服务器

JSch 连接服务器主要步骤如下

1、创建jsch对象
2、创建session会话
3、设置session配置
4、使用connect方法进行连接

实现原理

   1. 根据远程主机的IP地址,用户名和端口,建立会话(Session);

   2. 设置用户信息(包括密码和Userinfo),然后连接session;
   注:getSession()只是创建一个session,需要设置必要的认证信息之后,调用connect()才能建立连接。

   3. 在session上建立指定类型的通道(Channel);

   4. 设置channel上需要远程执行的Shell脚本,连接channel,就可以远程执行该Shell脚本;
   注:使用channel前需要先调用connect()进行连接。

    5. 读取远程执行Shell脚本的输出,然后依次断开channel和session的连接;

写了一个简单的例子,大家可以参考下
 /**
     * 连接服务器并执行shell命令
     * @param host 主机ip
     * @param port 端口
     * @param userName 用户名
     * @param password 密码
     * @param cmd shell命令
     * @return
     */
    public List<String> execCmd(String host, int port, String userName, String password,String cmd){
//        创建jsch对象
        JSch jsch = new JSch();
        ChannelExec channelExec=null;
        Session session=null;
        InputStream inputStream=null;
//        输出结果到字符串数组
        List<String> resultLines = new ArrayList<>();
//        创建session会话
        try {
            session = jsch.getSession(userName, host, port);
//            设置密码
            session.setPassword(password);
//            创建一个session配置类
            Properties sshConfig = new Properties();
//            跳过公钥检测
            sshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(sshConfig);
//            我们还可以设置timeout时间
            session.setTimeout(SESSION_TIMEOUT);
//            建立连接
            session.connect();
//            session建立之后,我们就可以执行shell命令,或者上传下载文件了,下面我来执行shell命令
            channelExec = (ChannelExec) session.openChannel("exec");
//            将shell传入command
            channelExec.setCommand(cmd);
//            开始执行
            channelExec.connect();
//            获取执行结果的输入流
            inputStream = channelExec.getInputStream();
            String result=null;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            while ((result = in.readLine()) != null) {
                resultLines.add(result);
                LOGGER.info("命令返回信息:{}", result);
            }
        }catch (Exception e){
            LOGGER.error("Connect failed, {}", e.getMessage());
            ArrayList<String> errorMsg = new ArrayList<>();
            errorMsg.add(e.getMessage());
            return errorMsg;
        }finally {
//            释放资源
            if (channelExec != null) {
                try {
                    channelExec.disconnect();
                } catch (Exception e) {
                    LOGGER.error("JSch channel disconnect error:", e);
                }
            }
            if (session!=null){
                try {
                    session.disconnect();
                } catch (Exception e) {
                    LOGGER.error("JSch session disconnect error:", e);
                }
            }
            if (inputStream!=null){
                try {
                    inputStream.close();
                } catch (Exception e) {
                    LOGGER.error("inputStream close error:", e);
                }
            }
        }
        return resultLines;
    }

我输入”pwd”的shell命令,可以看到在控制台的日志打印出结果
在这里插入图片描述

Channel通道类型

JSch常用的Channel通道类型有三种,ChannelShell、ChannelExec、ChannelSftp

ChannelShell、ChannelExec是用于执行命令的

ChannelSftp是用于上传下载文件的

ChannelShell和ChannelExec的区别: 前者是交互式的,在channel.connect()之前,需要获取outputStream和inputStream,然后outputstream发送命令,从instream中读取命令的结果(注意,发送命令之后,读取命令之前要等待一会儿,一般需要写个循环判断,每秒读一次,根据实际情况设置xx秒超时退出),但是有个坏处是,它执行就像登陆到vm上打印的信息一样,无论执行命令后的任何信息,它都会通过instream返回到客户端来,而你可能仅仅需要命令执行后的结果;于是就有了后者,非交互的,一次通道执行一条命令。


上传和下载demo

/**
     * 上传文件
     * @param session        session会话
     * @param directory     上传的目录
     * @param fileName      上传的文件名
     * @param uploadFile    要上传的文件
     */
    public boolean upload(Session session,String directory, String fileName, File uploadFile) {
        FileInputStream fileInputStream=null;
        ChannelSftp channelSftp=null;
        try {
            channelSftp= (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            LOGGER.info("start upload channel file!");
            channelSftp.cd(directory);
            fileInputStream = new FileInputStream(uploadFile);
            channelSftp.put(fileInputStream, fileName);
            return true;
        } catch (Exception e) {
            LOGGER.error("SFTPClient upload file failed, {}", e.getMessage(), e);
            return false;
        }finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (Exception e) {
                    LOGGER.error("File output stream close error, ", e);
                }
            }
            if (channelSftp!=null){
                channelSftp.disconnect();
            }
        }
    }




/**
     * 下载文件
     * @param session        session会话
     * @param directory     下载目录
     * @param fileName      下载的文件名
     * @param saveFile      存在本地的路径
     */
    public File download(Session session,String directory, String fileName, String saveFile) {
        ChannelSftp channelSftp=null;
        FileOutputStream fileOutputStream=null;
        try {
            channelSftp= (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            channelSftp.cd(directory);
            File file = new File(saveFile);
            if (file.exists()) {
                file.delete();
            }
            fileOutputStream = new FileOutputStream(file);
            channelSftp.get(fileName, fileOutputStream);
            return file;
        } catch (Exception e) {
            LOGGER.error("SFTPClient download file failed, {}", e.getMessage(), e);
            return null;
        }finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (Exception e) {
                    LOGGER.error("File output stream close error, ", e);
                }
            }
            if (channelSftp!=null){
                channelSftp.disconnect();
            }
        }
    }
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐