简介:
该项目是使用Java的GUI即swing构造的,借助jsch连接到远程的终端,通过读取对应的输入输出流,把本地的命令发送至远程终端,远程终端执行完命令后,再获取终端的输出日志到本地的JTextArea多行文本框中显示,可实现交互效果,具体的实现细节请看代码。

演示:

java 终端 java终端窗口_java

代码:
本项目使用Maven管理,需要用到第三方jar包,所以要添加jsch依赖到pom文件中,如下:

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

建立一个pojo类用来封装主机名、用户名、密码等:

class DestHost {
    private String host;
    private String username;
    private String password;
    private int port = 22;
    private int timeout = 60 * 60 * 1000;

    public DestHost(String host, String username, String password) {
        this.host = host;
        this.username = username;
        this.password = password;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

因为要与终端交互,故会话、输入输出流之类的定义为全局变量,并保持连接,使用到的全局变量有:

private static Session session = null;
private static ChannelShell channel = null;
private static PrintWriter printWriter = null;
private static BufferedInputStream bufferedInputStream = null;
private static JTextArea area = null;

private static boolean flag = true;
private static boolean mark = true;
private static int index = 0;
private static int old_len = 0;

使用jsch连接远程终端,代码如下:

// 与远程终端建立连接
    private static void buildConnection(DestHost destHost){
        JSch jsch = new JSch();
        try {
            session = jsch.getSession(destHost.getUsername(),destHost.getHost(),destHost.getPort());
            session.setPassword(destHost.getPassword());
            session.setConfig("StrictHostKeyChecking", "no");
            session.setTimeout(destHost.getTimeout());
            session.connect();

            channel = (ChannelShell) session.openChannel("shell");
            channel.setPty(true);
            channel.connect();

            bufferedInputStream = new BufferedInputStream(channel.getInputStream());
            printWriter = new PrintWriter(channel.getOutputStream(),true);
        } catch (JSchException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

主界面的构建代码如下:

// 构建GUI
    private static void buildMain(){
        JFrame frame = new JFrame("terminal");
        area = new JTextArea() {
            public void append(String str) {
                super.append(str);
                this.setCaretPosition(getDocument().getLength());
            }
        };
        area.setFont(new Font("宋体", Font.BOLD, 14));

        // 把文本框加入到滚动面板中,内容溢出时可拉取
        JScrollPane scrollPane = new JScrollPane(area);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setBounds(15, 0, 750, 540);
        JPanel panel = new JPanel(null);
        panel.add(scrollPane);

        // 为文本框添加监听键盘事件
        area.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {}
            @Override
            public void keyPressed(KeyEvent e) {}
            @Override
            public void keyReleased(KeyEvent e) {
                mark = false;
                if (flag) {
                    index = Math.min(area.getText().length(),old_len);
                    flag = false;
                }
                // 监听回车键
                if (e.getKeyChar() == '\n') {
                    area.paintImmediately(area.getBounds());
                    String content = area.getText();
                    // 获取输入的命令
                    content = content.substring(index);
                    // 发送给终端
                    printWriter.print(content);
                    printWriter.flush();
                    flag = true;
                    mark = true;
                }
            }
        });
        frame.add(panel);
        frame.setSize(800, 600);
        frame.setLocationRelativeTo(null);//设置居中显示
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

读取终端输出日志到本地文本框显示:

// 实时读取远程终端执行完命令后的输入流并更新到文本框里
    private static void read(){
        new Thread(() -> {
            while (true) { // 让该线程永久运行下去,定时更新文本框内容
                StringBuilder sb = new StringBuilder();
                byte[] bytes = new byte[1024];
                try {
                    int count = 0;
                    int len = bufferedInputStream.available();// 避免阻塞
                    int now = 0;
                    // 使用mark和reset方法,从而可以多次读取同一输入流
                    bufferedInputStream.mark(0);
                    while (now < len) {
                        count = bufferedInputStream.read(bytes);
                        now += count;
                        String s = new String(bytes,0,count);
                        // 用正则表达式过滤掉表示颜色的字符
                        String reg = "\\[\\d.*?m";
                        Pattern pattern = Pattern.compile(reg);
                        Matcher matcher = pattern.matcher(s);
                        s = matcher.replaceAll("");
                        sb.append(s);
                    }
                    if (mark && sb.length() > 0) {
                        refreshJTextArea(sb.toString());
                    }
                    bufferedInputStream.reset();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

该项目完整代码如下:

import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MyTerminal {
    private static Session session = null;
    private static ChannelShell channel = null;
    private static PrintWriter printWriter = null;
    private static BufferedInputStream bufferedInputStream = null;
    private static JTextArea area = null;

    private static boolean flag = true;
    private static boolean mark = true;
    private static int index = 0;
    private static int old_len = 0;

    public static void main(String[] args) {
        buildConnection(new DestHost("ip","username","password"));
        // 启动线程开始读取
        read();
        buildMain();
    }

    // 与远程终端建立连接
    private static void buildConnection(DestHost destHost){
        JSch jsch = new JSch();
        try {
            session = jsch.getSession(destHost.getUsername(),destHost.getHost(),destHost.getPort());
            session.setPassword(destHost.getPassword());
            session.setConfig("StrictHostKeyChecking", "no");
            session.setTimeout(destHost.getTimeout());
            session.connect();

            channel = (ChannelShell) session.openChannel("shell");
            channel.setPty(true);
            channel.connect();

            bufferedInputStream = new BufferedInputStream(channel.getInputStream());
            printWriter = new PrintWriter(channel.getOutputStream(),true);
        } catch (JSchException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 刷新文本框内容
    private static void refreshJTextArea(String content){
        area.setText("");
        area.append(content);
        area.paintImmediately(area.getBounds());// 让写入到文本框的内容立即显示
        old_len = area.getText().length();
    }

    // 实时读取远程终端执行完命令后的输入流并更新到文本框里
    private static void read(){
        new Thread(() -> {
            while (true) { // 让该线程永久运行下去,定时更新文本框内容
                StringBuilder sb = new StringBuilder();
                byte[] bytes = new byte[1024];
                try {
                    int count = 0;
                    int len = bufferedInputStream.available();// 避免阻塞
                    int now = 0;
                    // 使用mark和reset方法,从而可以多次读取同一输入流
                    bufferedInputStream.mark(0);
                    while (now < len) {
                        count = bufferedInputStream.read(bytes);
                        now += count;
                        String s = new String(bytes,0,count);
                        // 用正则表达式过滤掉表示颜色的字符
                        String reg = "\\[\\d.*?m";
                        Pattern pattern = Pattern.compile(reg);
                        Matcher matcher = pattern.matcher(s);
                        s = matcher.replaceAll("");
                        sb.append(s);
                    }
                    if (mark && sb.length() > 0) {
                        refreshJTextArea(sb.toString());
                    }
                    bufferedInputStream.reset();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    // 构建GUI
    private static void buildMain(){
        JFrame frame = new JFrame("terminal");
        area = new JTextArea() {
            public void append(String str) {
                super.append(str);
                this.setCaretPosition(getDocument().getLength());
            }
        };
        area.setFont(new Font("宋体", Font.BOLD, 14));

        // 把文本框加入到滚动面板中,内容溢出时可拉取
        JScrollPane scrollPane = new JScrollPane(area);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setBounds(15, 0, 750, 540);
        JPanel panel = new JPanel(null);
        panel.add(scrollPane);

        // 为文本框添加监听键盘事件
        area.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {}
            @Override
            public void keyPressed(KeyEvent e) {}
            @Override
            public void keyReleased(KeyEvent e) {
                mark = false;
                if (flag) {
                    index = Math.min(area.getText().length(),old_len);
                    flag = false;
                }
                // 监听回车键
                if (e.getKeyChar() == '\n') {
                    area.paintImmediately(area.getBounds());
                    String content = area.getText();
                    // 获取输入的命令
                    content = content.substring(index);
                    // 发送给终端
                    printWriter.print(content);
                    printWriter.flush();
                    flag = true;
                    mark = true;
                }
            }
        });
        frame.add(panel);
        frame.setSize(800, 600);
        frame.setLocationRelativeTo(null);//设置居中显示
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

class DestHost {
    private String host;
    private String username;
    private String password;
    private int port = 22;
    private int timeout = 60 * 60 * 1000;

    public DestHost(String host, String username, String password) {
        this.host = host;
        this.username = username;
        this.password = password;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

总结:
本项目还有许多不足,如使用的JTextArea是可编辑的,这意味着对于输出的日志信息可随意删除修改,不过,由于我在后台开了一个子线程让它永久运行(因为不知道如何监听终端的命令执行情况,所以想了这个暴力方法),并每隔一定时间让它读取终端的输入流显示在JTextArea中,且每次读取都从头开始读,会把终端所有输出的日志显示在JTextArea中;还有在连接终端的数据库输入密码时是明文输入,这个暂时还没有想到解决的办法。如果有什么更好的方式欢迎在评论区留言~~