xterm4.xx结合websocket

3,081 阅读2分钟

背景

前段时间,需要实现“web端的terminal”,后端技术是用websocket发送和处理数据。前端需要做的只是将用户输入的命令发送给后端,后端再将执行后的结果发送给前端。说白了就是说,前端只负责发送数据和显示结果数据(纯展示)。

之前的同事大概是基于xterm3.0的版本实现的,而且是把整个依赖包下载到本地;另外,网上关于xterm的教程真是少之又少,又看到xterm4.0在基于3.0的基础上修复了很多问题。出于提高项目的可维护性,于是就升级为xterm4.0

实现

安装

  • 安装xterm相关

yarn add xterm xterm-addon-attach xterm-addon-fit xterm-addon-search xterm-addon-web-links

xterm-addon-attach: 通过 Websocket 附加到运行进程的服务器

xterm-addon-fit: 将终端适配到html容器中

xterm-addon-search: 添加搜索功能

xterm-addon-web-links: 添加 Web 链接检测和交互

  • 安装rxjs相关

关于rxjs相关的知识就不在这个文章中表述,感兴趣的可以阅读RxJS - Subject

yarn add rxjs rxjs-hooks

实现步骤

  • 创建terminal容器
<div id="terminal" style={{ width: '100%', height: 600 }} />
  • 渲染terminal容器,并添加事件
renderTerminal = () => {
  // 根据容器的高度动态设置行数rows
  let container = document.getElementById('terminal');
  let height = container.clientHeight;
  let rows = parseInt(height / 18); //18是字体高度,根据需要自己修改

  // 渲染Terminal,并添加事件
  this.term = new XTerminal({
    rendererType: 'canvas',
    fontSize: 14,
    fontFamily: 'Consolas, "Courier New", monospace',
    bellStyle: 'sound',
    cursorBlink: true,
    rows,
  });

  const fitAddon = this.fitAddon;
  const webLinksAddon = this.webLinksAddon;
  const term = this.term;

  term.loadAddon(fitAddon);
  term.loadAddon(webLinksAddon);

  term.open(container);
  fitAddon.fit();

  term.onData((msg) => {
    this.input$.next(
      JSON.stringify({
        data: msg,
      })
    );
  });

  // 内容全屏显示-窗口大小发生改变时
  const resizeScreen = () => {
    try {
      fitAddon.fit();

      // 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定
      term.onResize(() => {
        this.input$.next({ resize: [this.term.cols, this.term.rows] });
      });
    } catch (e) {
      console.log('e', e.message);
    }
  };

  window.addEventListener('resize', resizeScreen);
};

  • 建立 websocket 连接,并绑定onopen、onmessage、onclose事件
setupConn = () => {
    const { id, token } = this.props
    // 建立 websocket 连接,并绑定事件
    let url = `wss://${sshUrl()}/ws?id=${id}&token=${token}`

    this.conn = new WebSocket(url)
    this.conn.onopen = () => {
        const startData = { Op: 'bind' }
        this.connected = true
        this.input$.next(startData)
     }
     this.conn.onmessage = (event) => {
         this.output$.next(JSON.parse(event.data))
     }

    this.conn.onclose = this.closeConn
}
  • 订阅 input对象(用来发送数据)、订阅output 对象(用来发送数据)、订阅 output 对象并将 output$ 接收的数据写入 terminal 容器中
 // 订阅input$对象
 this.subscription = this.input$.subscribe((data) => {
     this.sendMsg(data)
 })
 this.subscription.add(
     // 订阅output$对象, 将output$接收的数据写入terminal容器中
     this.output$.subscribe((data) => {
         data && this.term.write(data)
     })
 )
  • 监听terminal输入数据时,发送数据给websocket
term.onData((msg) => {
    this.input$.next(
        JSON.stringify({
            data: msg,
        })
    )
})
  • 在websocket的onmessage中输出数据
 this.conn.onmessage = (event) => {       
    this.output$.next(JSON.parse(event.data))
 }

效果图

参考资料