vue+noVNC实现VNC客户端

最近遇到一个需求:虚拟机运行成功后,需要在前端界面上弹出虚拟机的远程桌面(类似VNC客户端),在此做个记录~

1. 实现流程

图1-1 实现思想

连接地址:ws://localhost:8081/vnc/192.168.18.57:5900,使用nginx代理vnc至node服务,在转发至目标主机,也就是连接的虚拟机地址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连接断开的回调函数
    disconnectedFromServer(msg) {
      console.log('断开连接', msg);
      // clean是boolean指示终止是否干净。在发生意外终止或错误时 clean将设置为 false。
      if(msg.detail.clean){
        // 根据 断开信息的msg.detail.clean 来判断是否可以重新连接
      } else {
        // 这里做不可重新连接的一些操作
        console.log('连接不可用(可能需要密码)')
      }
      this.rfb = null;
      this.connectVnc();
    },
    // 连接成功的回调函数
    connectedToServer() {
      console.log('连接成功');
    },
    //连接vnc的函数
    connectVnc() {
      const PASSWORD = '';
      let rfb = new RFB(document.getElementById('screen'), this.url, {
        // 向vnc 传递的一些参数,比如说虚拟机的开机密码等
        credentials: {password: PASSWORD}
      });
      rfb.addEventListener('connect', this.connectedToServer);
      rfb.addEventListener('disconnect', this.disconnectedFromServer);
      // scaleViewport指示是否应在本地扩展远程会话以使其适合其容器。禁用时,如果远程会话小于其容器,则它将居中,或者根据clipViewport它是否更大来处理。默认情况下禁用。
      rfb.scaleViewport = true;
      // 是一个boolean指示是否每当容器改变尺寸应被发送到调整远程会话的请求。默认情况下禁用
      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;

/** 本机 ip 地址 */
const localhost = '127.0.0.1';

/** 开放的 vnc websocket 转发端口 */
const vnc_port = 8112;

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

/** 建立基于 vnc_port 的 websocket 服务器 */
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) {
    /** 获取请求url */
    const url = req.url;
    console.log("====", url);

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

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

    /** 打印日志 */
    console.log(`WebSocket 连接 : 版本 ${client.protocolVersion}, 协议 ${client.protocol}`);

    /** 连接到 VNC Server */
    const target = net.createConnection(port, host, function () {
        console.log('连接至目标主机');
    });

    /** 数据事件 */
    target.on('data', function (data) {
        try {
            client.send(data);
        } catch (error) {
            console.log('客户端已关闭,清理到目标主机的连接');
            target.end();
        }
    });

    /** 结束事件 */
    target.on('end', function () {
        console.log('目标主机已关闭');
        client.close();
    });

    /** 错误事件 */
    target.on('error', function () {
        console.log('目标主机连接错误');
        target.end();
        client.close();
    });

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

    /** 关闭事件 */
    client.on('close', function (code, reason) {
        console.log(`WebSocket 客户端断开连接:${code} [${reason}]`);
        target.end();
    });

    /** 错误事件 */
    client.on('error', function (error) {
        console.log(`WebSocket 客户端出错:${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句)配置允许创建websocket
  proxy_set_header Upgrade websocket;
  proxy_set_header Connection Upgrade;
}
4. 结果
图4-1 运行结果截图
参考文章

no-vnc和node.js实现web远程桌面的完整步骤

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容