Xterm 官方文档
Xterm.js (xtermjs.org)
Jsch 官方文档
JSch - Java Secure Channel (jcraft.com)
对于Websocket不熟悉的可以先看一下这篇文章👉spring中利用websocket打造最简易的双向通讯-CSDN博客
👇最终实现的效果如下图所示👇
后端部分
这里使用spring boot框架演示
建立好框架之后,引入pom
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
新建JschUtil.java
首先连接服务器必须要的配置先写到属性当中
@Slf4j
public class JschUtil {
// 主机ip
static String host = "IP地址";
// 主机端口号
static int port = 22;
// 主机账号
static String username = "root";
// 主机密码
static String password = "123456";
static JSch jSch = new JSch();
// session对象
static Session session;
// JAVA与主机的连接通道
static Channel channel;
static ChannelShell shell;
// 线程池,后续需要新开线程,去持续监听模拟连接的输出,否则会造成主线程堵塞
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
}
开启一个连接
此处WebSocketServer
用到文章一开始提到的建立Websocket的java类,这里假设用户ID为1的连接,始终给ID为1的用户响应,作为此DEMO演示。
@Slf4j
public class JschUtil {
// ......
public static void getConnectedSession() throws JSchException, SftpException {
// 根据主机账号、ip、端口获取一个Session对象
session = jSch.getSession(username, host, port);
// 存放主机密码
session.setPassword(password);
// 首次连接,去掉公钥确认
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 超时连接时间为3秒
session.setTimeout(3000);
session.connect();
shell = (ChannelShell) session.openChannel("shell");
shell.connect();
//开新线程持续监听
executorService.execute(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = shell.getInputStream();
byte[] tmp = new byte[1024];
int i = 0;
// 持续监听
while ((i = inputStream.read(tmp, 0, 1024)) != -1) {
// 始终给ID为1的用户发送。
if (WebSocketServer.onlineSessionClientMap.containsKey("1")) {
WebSocketServer.onlineSessionClientMap.get("1").getBasicRemote().sendText(new String(tmp, 0, i));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
close();
}
}
});
}
}
连接成功之后响应其他的Linux命令
@Slf4j
public class JschUtil {
// ......
public static void execCommand(String command) {
try {
OutputStream os = shell.getOutputStream();
os.write(command.getBytes());
os.flush();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
关闭响应的方法👇
@Slf4j
public class JschUtil {
// ......
public static void close() {
if (channel != null && channel.isConnected()) {
channel.disconnect();
}
if (shell != null && shell.isConnected()) {
shell.disconnect();
}
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
此处使用的WebSocketServer.java和文章开头提到的另一篇文章使用类似,如下
@ServerEndpoint(value = "/test/{id}")
@Slf4j
@Component
public class WebSocketServer {
//在线客户端集合
public static final Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();
/**
* 连接创建成功
*
* @param id
* @param session
*/
@OnOpen
public void onOpen(@PathParam("id") String id, Session session) {
System.out.println("开启连接" + id);
onlineSessionClientMap.put(id, session);
try {
// 一开始建立的时候连接服务器
JschUtil.getConnectedSession();
} catch (JSchException | SftpException e) {
throw new RuntimeException(e);
}
}
/**
* 连接关闭回调
*
* @param id
* @param session
*/
@OnClose
public void onClose(@PathParam("id") String id, Session session) {
//从map集合中移除
System.out.println("断开连接" + id);
onlineSessionClientMap.remove(id);
JschUtil.close();
}
/**
* 收到消息后的回调
*
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
Message msg = JSONObject.parseObject(message, Message.class);
if (msg != null && msg.getTo() != null) {
JschUtil.execCommand(msg.getContent());
}
}
/**
* 发生错误时的回调
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
}
}
前端部分
下载依赖
使用yarn
yarn add @xterm/xterm @xterm/addon-web-links @xterm/addon-fit
使用npm
npm i @xterm/xterm @xterm/addon-web-links @xterm/addon-fit
本篇使用vue3演示
<!-- App.vue -->
<script setup lang="ts">
import { Terminal } from '@xterm/xterm'
import { WebLinksAddon } from '@xterm/addon-web-links';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css'
let term: Terminal
const ws = new WebSocket('ws://localhost:8080/test/1');
ws.onopen = () => {
console.log('连接成功');
};
ws.onclose = (e) => {
console.log(e, '连接关闭');
};
ws.onmessage = (e) => {
term.write(e.data);
};
onMounted(() => {
term = new Terminal({
rows: 60,
cols: 160,
convertEol: true,
cursorBlink: true,
cursorStyle: "bar", // 光标样式 'block' | 'underline' | 'bar' | null
});
term.open(document.getElementById('terminal') as HTMLElement);
term.loadAddon(new WebLinksAddon());
term.loadAddon(new FitAddon());
term.focus() //自动聚焦
term.write('connecting...\n') //一开始显示连接中
// 每一次输入都实时传输到后端和服务器通信
term.onData(data => {
ws.send(JSON.stringify({
from: '1',
to: '1',
content: data
}))
})
})
</script>
<template>
<div id="terminal">
</div>
</template>
<style scoped></style>
完成!