vue+noVNC實(shí)現(xiàn)VNC客戶端

最近遇到一個(gè)需求:虛擬機(jī)運(yùn)行成功后,需要在前端界面上彈出虛擬機(jī)的遠(yuǎn)程桌面(類似VNC客戶端),在此做個(gè)記錄~

1. 實(shí)現(xiàn)流程

圖1-1 實(shí)現(xiàn)思想

連接地址:ws://localhost:8081/vnc/192.168.18.57:5900,使用nginx代理vnc至node服務(wù),在轉(zhuǎn)發(fā)至目標(biāo)主機(jī),也就是連接的虛擬機(jī)地址192.168.18.57:5900

2. 前端

安裝 npm install @novnc/novnc

<template>
  <div class="full">
    <div id="screen" class="full"></div>
  </div>
</template>

<script>
import RFB from '@novnc/novnc/core/rfb';

export default {
  name: 'Novnc',
  data() {
    return {
      url: '',
      rfb: null
    }
  },
  methods: {
    getUrl(host) {
      let protocol = '';
      if (window.location.protocol === 'https:') {
        protocol = 'wss://';
      } else {
        protocol = 'ws://';
      }
      // 加window.location.host可以走vue.config.js的代理,ws://localhost:8081/vnc/192.168.18.57:5900
      const wsUrl = `${protocol}${window.location.host}/vnc/${host}`;
      console.log(wsUrl);
      return wsUrl;
    },
    // vnc連接斷開(kāi)的回調(diào)函數(shù)
    disconnectedFromServer(msg) {
      console.log('斷開(kāi)連接', msg);
      // clean是boolean指示終止是否干凈。在發(fā)生意外終止或錯(cuò)誤時(shí) clean將設(shè)置為 false。
      if(msg.detail.clean){
        // 根據(jù) 斷開(kāi)信息的msg.detail.clean 來(lái)判斷是否可以重新連接
      } else {
        // 這里做不可重新連接的一些操作
        console.log('連接不可用(可能需要密碼)')
      }
      this.rfb = null;
      this.connectVnc();
    },
    // 連接成功的回調(diào)函數(shù)
    connectedToServer() {
      console.log('連接成功');
    },
    //連接vnc的函數(shù)
    connectVnc() {
      const PASSWORD = '';
      let rfb = new RFB(document.getElementById('screen'), this.url, {
        // 向vnc 傳遞的一些參數(shù),比如說(shuō)虛擬機(jī)的開(kāi)機(jī)密碼等
        credentials: {password: PASSWORD}
      });
      rfb.addEventListener('connect', this.connectedToServer);
      rfb.addEventListener('disconnect', this.disconnectedFromServer);
      // scaleViewport指示是否應(yīng)在本地?cái)U(kuò)展遠(yuǎn)程會(huì)話以使其適合其容器。禁用時(shí),如果遠(yuǎn)程會(huì)話小于其容器,則它將居中,或者根據(jù)clipViewport它是否更大來(lái)處理。默認(rèn)情況下禁用。
      rfb.scaleViewport = true;
      // 是一個(gè)boolean指示是否每當(dāng)容器改變尺寸應(yīng)被發(fā)送到調(diào)整遠(yuǎn)程會(huì)話的請(qǐng)求。默認(rèn)情況下禁用
      rfb.resizeSession = true;
      this.rfb = rfb;
    }
  },
  mounted() {
    this.url = this.getUrl(this.$route.params.host);
    this.connectVnc();
  },
  beforeDestroy() {
    this.rfb && this.rfb.disconnect();
  }
}
</script>
2. node
/** 引入 http 包 */
const http = require('http');

/** 引入 net 包 */
const net = require('net');

/** 引入 websocket 類 */
const WebSocketServer = require('ws').Server;

/** 本機(jī) ip 地址 */
const localhost = '127.0.0.1';

/** 開(kāi)放的 vnc websocket 轉(zhuǎn)發(fā)端口 */
const vnc_port = 8112;

/** 打印提示信息 */
console.log(`成功創(chuàng)建 WebSocket 代理 : ${localhost} : ${vnc_port}`);

/** 建立基于 vnc_port 的 websocket 服務(wù)器 */
const vnc_server = http.createServer();
vnc_server.listen(vnc_port, function () {
    const web_socket_server = new WebSocketServer({server: vnc_server});
    web_socket_server.on('connection', web_socket_handler);
});

/** websocket 處理器 */
const web_socket_handler = function (client, req) {
    /** 獲取請(qǐng)求url */
    const url = req.url;
    console.log("====", url);

    /** 截取主機(jī)地址 */
    const host = url.substring(url.indexOf('/') + 1, url.indexOf(':'));

    /** 截取端口號(hào) */
    const port = Number(url.substring(url.indexOf(':') + 1));

    /** 打印日志 */
    console.log(`WebSocket 連接 : 版本 ${client.protocolVersion}, 協(xié)議 ${client.protocol}`);

    /** 連接到 VNC Server */
    const target = net.createConnection(port, host, function () {
        console.log('連接至目標(biāo)主機(jī)');
    });

    /** 數(shù)據(jù)事件 */
    target.on('data', function (data) {
        try {
            client.send(data);
        } catch (error) {
            console.log('客戶端已關(guān)閉,清理到目標(biāo)主機(jī)的連接');
            target.end();
        }
    });

    /** 結(jié)束事件 */
    target.on('end', function () {
        console.log('目標(biāo)主機(jī)已關(guān)閉');
        client.close();
    });

    /** 錯(cuò)誤事件 */
    target.on('error', function () {
        console.log('目標(biāo)主機(jī)連接錯(cuò)誤');
        target.end();
        client.close();
    });

    /** 消息事件 */
    client.on('message', function (msg) {
        target.write(msg);
    });

    /** 關(guān)閉事件 */
    client.on('close', function (code, reason) {
        console.log(`WebSocket 客戶端斷開(kāi)連接:${code} [${reason}]`);
        target.end();
    });

    /** 錯(cuò)誤事件 */
    client.on('error', function (error) {
        console.log(`WebSocket 客戶端出錯(cuò):${error}`);
        target.end();
    });
};
3. nginx
location /vnc/ {
  # rewrite         ^.+iot/?(.*)$ /$1 break;
  add_header      Access-Control-Allow-Origin *;
  add_header      Access-Control-Allow-Headers "Accept, X-Token, Content-Type";
  add_header      Access-Control-Allow-Methods "GET, POST, DELETE, PATCH, PUT, OPTIONS";
  proxy_pass      http://127.0.0.1:8112/;

  # (以下2句)配置允許創(chuàng)建websocket
  proxy_set_header Upgrade websocket;
  proxy_set_header Connection Upgrade;
}
4. 結(jié)果
圖4-1 運(yùn)行結(jié)果截圖
參考文章

no-vnc和node.js實(shí)現(xiàn)web遠(yuǎn)程桌面的完整步驟

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容