xterm+websocket+theading实现前端调用linux终端

xterm展示窗口,绑定从服务端获取到的流-》客户端和服务端建立websocket连接-》paramiko连接服务器-》获取流-》通过send发送给客户端-》客户端通过通道接收发送消息-》服务端接收消息发送到linux

1.xterm安装

npm install --save xterm   xterm-addon-fit xterm-addon-attach

2.vue代码

<template>
  <div class="box">
    <div id="xterm" class="xterm-box"></div>
  </div>
</template>

<script>
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import 'xterm/css/xterm.css'
import 'xterm/lib/xterm.js'

export default {
  data() {
    return {
      term: null,
      socket: '',
      WebSocketUrl: 'ws:localhost:5000/websocketconn',
      //WebSocketUrl: 'ws:192.168.5.10:8080/terminal/17317181717', //ws接口ws:192.168.5.10:8080/terminal/17317181717
      // 心跳
      lockReconnect: false, //是否真正建立连接
      timeout: 60 * 1000, //60秒一次心跳
      timeoutObj: null, //心跳心跳倒计时
      serverTimeoutObj: null, //心跳倒计时
      timeoutnum: null, //断开 重连倒计时
    }
  },
  mounted() {
    this.init(this.WebSocketUrl)
  },
  //跑路前清除定时器
  beforeDestroy() {
    this.close()
    clearTimeout(this.timeoutObj)
    clearTimeout(this.serverTimeoutObj)
    clearTimeout(this.timeoutnum)
  },
  methods: {
    // 心跳函数--------------
    reconnect() {
      //重新连接
      var that = this
      if (that.lockReconnect) {
        return
      }
      that.lockReconnect = true
      //没连接上会一直重连,设置延迟避免请求过多
      that.timeoutnum && clearTimeout(that.timeoutnum)
      that.timeoutnum = setTimeout(function () {
        //新连接
        that.init(that.WebSocketUrl)
        that.lockReconnect = false
      }, 2000)
    },
    reset() {
      //重置心跳
      var that = this
      //清除时间
      clearTimeout(that.timeoutObj)
      clearTimeout(that.serverTimeoutObj)
      //重启心跳
      that.start()
    },
    start() {
      //开启心跳
      var self = this
      self.timeoutObj && clearTimeout(self.timeoutObj)
      self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj)
      self.timeoutObj = setTimeout(function () {
        //这里发送一个心跳,后端收到后,返回一个心跳消息,
        if (self.socket.readyState == 1) {
          //如果连接正常,有事没事发ping,具体根据要求
          //self.socket.send('ping')
        } else {
          //否则重连
          self.reconnect()
        }
        self.serverTimeoutObj = setTimeout(function () {
          //超时关闭
          self.close()
        }, self.timeout)
      }, self.timeout)
    },
    //-----------------

    initXterm() {
      if (this.term) {
        this.term.dispose()
      }
      let height = document.body.clientHeight
      this.term = new Terminal({
        rendererType: 'canvas', //渲染类型
        rows: 35, //行数 18是字体高度,根据需要自己修改
        convertEol: true, //启用时,光标将设置为下一行的开头
        scrollback: 800, //终端中的回滚量
        disableStdin: false, //是否应禁用输入
        cursorStyle: 'underline', //光标样式
        cursorBlink: true, //光标闪烁
        tabStopWidth: 8, //制表宽度
        screenKeys: true,
        theme: {
          foreground: 'yellow', //字体
          background: '#060101', //背景色
          cursor: 'help', //设置光标
        },
      })
      this.term.writeln('Welcome to use Superman. ')
      this.term.open(document.getElementById('xterm'))
      const fitAddon = new FitAddon()
      this.term.loadAddon(fitAddon)
      fitAddon.fit() //全屏

      //window.addEventListener('resize', this.resizeScreen)

      // 支持输入与粘贴方法
      let _this = this //一定要重新定义一个this,不然this指向会出问题
      let code = ''
      let codeArr = '' //存储输入集合
      this.term.onData(function (key) {
        
        _this.socket.onsend(key)
        
        //codeArr += key
        //_this.term.write(key)
        // let order = { operate: 'command', command: key }
        //这里key值是你输入的值,数据格式order一定要找后端要!!!!

        //if (key.length > 1) _this.term.write(key) //粘贴

        //console.log(key, JSON.stringify(key))
        // alert(key)
       
      })
    },


    init(url) {
      // 实例化socket
      this.socket = new WebSocket(url)
      // 监听socket连接
      this.socket.onopen = this.open
      // 监听socket错误信息
      this.socket.onerror = this.error
      // 监听socket消息this.getMessage
      this.socket.onmessage =(mes)=>{
        this.term.write(mes.data) //这里write也许不是固定的,失败后找后端看一下该怎么往term里面write
       //收到服务器信息,心跳重置
        this.reset()
      }
      // 发送socket消息
      this.socket.onsend = (order)=>{
        // alert(order)
        this.socket.send(order)
      }
    },
    open: function () {
      alert("success")
      console.log('socket连接成功')
      //this.term.write('Welcome to use')
      this.initXterm()
      //开启心跳
      this.socket.send("")
      this.start()
    },
    error: function () {
      console.log('连接错误')
      //重连
      this.reconnect()
    },
    close: function () {
      alert("close")
      this.socket.close()
      console.log('socket已经关闭')
      console.log(e.code + ' ' + e.reason + ' ' + e.wasClean)
      //重连
      this.reconnect()
    },

    getMessage: function (msg) {
      alert("22")
      console.log("****")
      console.log(msg)
      //msg是返回的数据

      //msg = JSON.parse(msg.data)
      //msg = msg.data
      //console.log(msg)
      //this.socket.send('ping') //有事没事ping一下,看看ws还活着没
      //switch用于处理返回的数据,根据返回数据的格式去判断
      // switch (msg['operation']) {
      //   case 'stdout':
      //     this.term.write(msg['data']) //这里write也许不是固定的,失败后找后端看一下该怎么往term里面write
      //     break
      //   default:
      //     console.log('Unexpected message type:', msg) //但是错误是固定的。。。。
      // }

      this.term.write(msg['data']) //这里write也许不是固定的,失败后找后端看一下该怎么往term里面write
      //收到服务器信息,心跳重置
      this.reset()
    },
    // send: function (order) {
    //   //console.log(order)
    //   this.socket.send(order)
    // },
    resizeScreen(size) {
      console.log('size', size)
      var fitAddon = new FitAddon()
      term.loadAddon(fitAddon)
      try {
        fitAddon.fit()
        // 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定
        term.onResize((size) => {
          //_this.onSend({ Op: 'resize', Cols: size.cols, Rows: size.rows })
        })
      } catch (e) {
        console.log('e', e.message)
      }
    },
  },
}
</script>

</script>

<!-- <style lang="scss" scoped>
.box {
  width: 100%;
  height: 100%;
  .xterm-box {
    width: 100%;
    height: 100%;
  }
}
</style> -->

3.flask代码

from flask_sockets import Sockets
# app = getapp()
manager = Manager(app)
Migrate(app, db)
sockets = Sockets(app)

import paramiko
def _ssh(host,username,password,port=22):
    client = paramiko.SSHClient()

    #2.解决问题:如果之前没有,连接过的ip,会出现选择yes或者no的操作,
    ##自动选择yes
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
        #3.连接服务器
        client.connect(hostname=host,
                    port=22,
                    username=username,
                    password=password)
        channle = client.invoke_shell(term='xterm')
        return channle
    except:
         return False


def recv_ssh_msg(channle,ws):
    '''
    channle: 建立好的SSH连接通道 这个函数会不停的接收ssh通道返回的命令 返回到前端的ws套接字里
    '''
    while not channle.exit_status_ready():
        try:
            buf = channle.recv(1024) # 接收命令的执行结果
           
            print("buff:::",buf.decode())
            ws.send(buf.decode()) # 向Websocket通道返回 except:
            break
        except:
            pass
plist=[]
import threading
@sockets.route('/websocketconn')
def webssh(ws):
    channle = _ssh("43.143.177.197", username="root", password="123456789Gg")
   
    '''
1: 接收前端(ws)的命令,发给后台(ssh) 2: 接收后台的返回结果,给到前端
'''
    while not ws.closed:
        message = ws.receive()  # 接收到消息
        t = threading.Thread(target=recv_ssh_msg,args=(channle,ws)) 
        t.setDaemon(True)
        t.start() # 线程开启
        channle.send(message) # 由SSH通道转交给Linux环境 else: # 连接断开 跳出循环
         



if __name__=="__main__":
    # manager.run()
    # app.run()
    from gevent import pywsgi
    from geventwebsocket.handler import WebSocketHandler
    server = pywsgi.WSGIServer(("localhost", 5000), app, handler_class=WebSocketHandler)
    print("web server start ... ")
    server.serve_forever()

你可能感兴趣的:(前端,websocket,linux)