美文网首页
xterm实现单个页面多个webssh

xterm实现单个页面多个webssh

作者: 家里有棵核桃树 | 来源:发表于2021-03-26 10:56 被阅读0次

    xterm是一个使用TypeScript编写的前端终端组件。

    1、demo主要介绍

    本文demo主要实现一个页面可以有webssh窗口,前端部分主要利用xterm通过socket.io-client和后端通信,后端部分使用nodejs+utf8+socket.io+ssh2

    界面效果
    点击界面中创建WEBSSH按钮,即可新建一个webssh窗口。为了方便验证功能,本demo中后端连接的服务器信息是写死的,可根据实际情况在前端在创建webssh调用addTerm方法时,传入服务器信息。
    简要流程

    2、前端实现

    基于vue项目,前端主要依赖包:xterm xterm-addon-fit socket.io-client,使用前请install。

    <template>
      <div class="container">
        <p>
          <button type="button" @click="addSsh">创建WEBSSH</button>
          <small style="margin-left: 10px;">ws地址不变,可以创建多个webssh窗口,且这些窗口互不影响。</small>
        </p>
        <ul class="list">
          <li
            v-for="item in sshList"
            :key="item.label"
            :class="{active: sshActiveInfo.label === item.label}"
            @click="changeSsh(item)">
            {{item.label}}
          </li>
        </ul>
        <div style="flex: 1; display: flex; padding-bottom: 10px;">
          <div
            style="padding-left: 10px; padding-right: 10px;"
            v-for="item in sshList"
            :class="item.selector"
            v-show="sshActiveInfo.label === item.label">
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import 'xterm/css/xterm.css'
    import io from 'socket.io-client';
    import { Terminal } from 'xterm';
    import { FitAddon } from "xterm-addon-fit";
    
    export default {
      name: 'WebShell',
      data() {
        return {
          sshList: [],
          sshActiveInfo: {}
        }
      },
      methods: {
        addSsh() {
          let index = this.sshList.length + 1;
          let info = {
            label: `WEBSSH-${index}`,
            isActive: true,
            selector: `xterm-${index}`,
            term: null
          };
          this.sshList.push(info);
          this.sshActiveInfo = info;
          this.$nextTick(() => {
            this.addTerm(info);
          });
        },
        changeSsh(item) {
          this.sshActiveInfo = item;
          item.term.focus();
        },
        addTerm(item) {
          item.term = new Terminal({
            cursorBlink: true,
            // cols: 100,
            // rows: 20,
            scrollback: 50
          });
          item.fitAddon = new FitAddon();
          item.term.loadAddon(item.fitAddon);
          item.term.open(document.querySelector(`.${item.selector}`));
          item.fitAddon.fit();
          item.term.focus();
    
          // socket.on()方法 和 socket.emit()在前后端是成对出现的,通过监听的标识符来显示不同模块的数据,就不用一个页面利用多个ws了。
          // socket.emit()用于向服务端(后端)发送数据, 第一个参数为监听的标识
          this.socket.emit("createNewServer", {
            msgId: item.selector,
            cols: item.term.cols,
            rows: item.term.rows
          });
          // 只要有键入 就会触发该事件
          item.term.onData(data =>  {
            this.socket.emit(item.selector, data);
          });
          // socket.on()用于监听获取服务端(后端)发送过来的数据, 第一个参数为监听的标识
          this.socket.on(item.selector, function (data) {
            // 用'\n'换行
            item.term.write(data);
          });
          window.addEventListener('resize', () => {
            item.fitAddon.fit()
            this.socket.emit('resize', { cols: item.term.cols, rows: item.term.rows });
          }, false);
        }
      },
      mounted() {
        this.socket = io('http://localhost:5000'); // ws://localhost:5000
      },
      beforeDestroy() {
        this.socket.close();
        this.sshList.map(item => item.term.dispose());
      }
    }
    </script>
    
    <style scoped>
    .list {
      padding: 0;
      margin: 0 0 15px 0;
      list-style: none;
    }
    .list::after {
      display: table;
      content: '';
    }
    .list li {
      float: left;
      margin-right: 20px;
      font-size: 14px;
      cursor: pointer;
    }
    .list li:hover {
      color: #60bb63;
    }
    .list li.active {
      color: #60bb63;
    }
    [class*="xterm-"] {
      flex: 1;
    }
    </style>
    

    3、后端实现

    前端主要依赖包:utf8 ssh2 socket.io,使用前请install。
    ssh2用来实现nodejs和服务器进行连接和通信。
    utf8用来实现服务器返回的命令执行结果解码。
    socket.io用来实现后端和前端ws全双工通信,通过传入不同的socket-msgId来实现信息标识,就可以实现单页面多个webssh只利用一个websocket。后端使用ws这个库也可以实现同样的效果,只是使用ws库要达到这个效果,客户端会创建多个ws实例而已。

    /**用来实现多个webssh功能**/
    const http = require('http').Server(app);
    const io = require('socket.io')(http, {cors: true});
    const utf8 = require('utf8');
    const SSHClient = require('ssh2').Client;
    
    const serverInfo = {
      host: '192.168.18.141',
      port: 22,
      username: 'root',
      password: '就不告诉你'
    };
    
    function createNewServer(machineConfig, socket) {
      var ssh = new SSHClient();
      let {msgId} = machineConfig;
      ssh
        .on('ready', function () {
          socket.emit(msgId, '\r\n*** ' + serverInfo.host + ' SSH CONNECTION ESTABLISHED ***\r\n');
          // ssh设置cols和rows处理界面输入字符过长显示问题
          ssh.shell({cols: machineConfig.cols, rows: machineConfig.rows}, function (err, stream) {
            if (err) {
              return socket.emit(msgId, '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
            }
            socket.on(msgId, function (data) {
              stream.write(data);
            });
            stream.on('data', function (d) {
              socket.emit(msgId, utf8.decode(d.toString('binary')));
            }).on('close', function () {
              ssh.end();
            });
            socket.on('resize', function socketOnResize (data) {
              stream.setWindow(data.rows, data.cols);
            });
          })
        })
        .on('close', function () {
          socket.emit(msgId, '\r\n*** SSH CONNECTION CLOSED ***\r\n');
        })
        .on('error', function (err) {
          console.log(err);
          socket.emit(msgId, '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
        }).connect(serverInfo);
    }
    
    io.on('connection', function (socket) {
      socket.on('createNewServer', function (machineConfig) {
        // 新建一个ssh连接
        console.log("createNewServer")
        createNewServer(machineConfig, socket);
      })
    
      socket.on('disconnect', function () {
        console.log('user disconnected');
      });
    })
    
    http.listen(5000, function () {
      console.log('listening on * 5000');
    });
    

    4、遗留问题

    1、浏览器resize后,webshell窗口宽高自适应、命令显示的问题;

    5、友情链接

    相关文章

      网友评论

          本文标题:xterm实现单个页面多个webssh

          本文链接:https://www.haomeiwen.com/subject/cwhjhltx.html