目的
诸如 cmd.exe/bash 等程序是在控制台上运行交互的,我们将把控制台的交互能力完全移动到 Web 界面上来,实现在网页上对一个进程进行管理(类似于一个终端)。
效果图
这是执行 ls -al 命令的结果在网页上显示。
实现方案
child_process 子进程 模块用于启动 bash/cmd.exe,并监听输入输出流。
使用 socket.io (Websocket 通信)实现与前端的数据传输。
使用 xtermjs 实现网页上的终端组件,实现Linux终端命令、颜色等显示。
基础流程图如下:
核心代码
既然方案已经有了,那么就可以实现方案,核心代码大概是这样。
arduino
// 注意:这只是筛选的部分核心代码,并非可直接运行的实现代码
// 初始化 HTTP 服务
const koaApp = koa.initKoa()
const httpServer = http.createServer(koaApp.callback())
httpServer.listen(config.port, config.ip)
// 初始化 Websocket 服务
const io = new Server(httpServer, {
serveClient: false,
pingInterval: 5000,
pingTimeout: 5000,
cookie: false,
path: '/socket.io',
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE']
}
})
// 创建子进程
const process = spawn('bash', [], {
cwd: '.',
stdio: 'pipe',
windowsHide: true
})
// 监听子进程的输入输出流,并且监听退出事件,同步给前端进程状态
// io.emit 是 Socket.io 发送数据的方法
process.stdout.on('data', (text) => io.emit('instance/stdout', text))
process.stderr.on('data', (text) => io.emit('instance/stdout', text))
process.on('exit', (code) => io.emit('instance/exit', code))
前端我们先实现控制台组件
xml
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello$ ')
// 这里还需要监听组件的按键事件,将按下的每一个键全部传递到后端
// 注意不是以一行为单位传输,而是每一次按键。
</script>
再实现Socket.io的连接与数据收发
javascript
// Websocket 连接
const addr = 'ws://localhost:8080'
console.log('浏览器正在连接', addr)
const socket = io(addr, {}).connect()
socket.on('connect', () => {
console.log('[WS->Server] Websocket 成功连接')
})
socket.on('disconnect', () => {
console.log('[WS->Server] Websocket 连接断开')
})
// 将后端发来的程序输出流直接输出到 Term 组件
this.socket.on('instance/stdout', (packet) => {
this.term.write(packet.data)
})
小结
这样,我们就基本实现了一个最最简单的 Web 终端,接下来通过改变启动进程的命令,就可以实现运行各种命令并且能够实时交互啦!
我们可以运行各种控制台程序在 Web 界面上,并且实现交互的功能。
模拟 TTY(teletypewriter)
什么是TTY(itsfoss.com/what-is-tty...)?为什么需要这玩意?
简单来说,模拟TTY就是虚拟一个真实的终端设备(上世纪那种古老的终端DOS显示器),来实现一个完整的虚拟控制台,从而可以在显示屏上播放图片,动画,色彩以及窗口等。
我们的实现原理是通过监听进程的输入输出流(stdin/stdout)来实现的,这是最简单的进程间通信模型,并非是一个完美的解决方案。
如果想在我们现在的 Web 控制台上播放高级的终端动画(比如复杂的进度条之类的),那单纯靠进程的输入输出是无法实现的,因为它没有"行"和"高"的概念,如果我们想实现真正的拥有高度和宽度的设备显示器控制台,就必须实现模拟TTY。
此时,我们的后端架构要变成这样,多一层 TTY 模拟。
核心代码实现
使用微软研发的 github.com/microsoft/n... 库完成这个需求。
php
// 启动进程
var shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
// 创建一个虚拟终端设备(TTY),并且设置高宽,并通过命令执行进程
var ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
// 此时的数据输出包含了颜色代码,排版符号等各种数据。
// 可以实现在 Web 终端上播放特效以及光标等动态更新等。
ptyProcess.on('data', function(data) {
// 发送给前端
io.emit('instance/stdout', text)
});
// 向伪终端发送命令
ptyProcess.write('ls\r');
TTY 实现结果
只要实现TTY,我们就可以执行这种及其复杂的 Linux 程序了,可以说就是一个没有基于 SSH 客户端的一个模拟终端!
支持 Tab 补全,Ctrl,Alt,F1~F12 等功能键。
真实案例
有一个开源项目正是使用了这种设计方案,有兴趣的小伙伴可以参考哦,此项目还有更多设计方案可以供各位学习使用,喜欢的小伙伴可以点个星星。