简介:
该项目是使用Java的GUI即swing构造的,借助jsch连接到远程的终端,通过读取对应的输入输出流,把本地的命令发送至远程终端,远程终端执行完命令后,再获取终端的输出日志到本地的JTextArea多行文本框中显示,可实现交互效果,具体的实现细节请看代码。
演示:
代码:
本项目使用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中;还有在连接终端的数据库输入密码时是明文输入,这个暂时还没有想到解决的办法。如果有什么更好的方式欢迎在评论区留言~~