最近遇到一個需求:虛擬機運行成功后,需要在前端界面上彈出虛擬機的遠程桌面(類似
VNC客戶端
),在此做個記錄~
1. 實現(xiàn)流程
圖1-1 實現(xiàn)思想
連接地址:
ws://localhost:8081/vnc/192.168.18.57:5900
,使用nginx代理vnc
至node服務,在轉發(fā)至目標主機,也就是連接的虛擬機地址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連接斷開的回調函數(shù)
disconnectedFromServer(msg) {
console.log('斷開連接', msg);
// clean是boolean指示終止是否干凈。在發(fā)生意外終止或錯誤時 clean將設置為 false。
if(msg.detail.clean){
// 根據(jù) 斷開信息的msg.detail.clean 來判斷是否可以重新連接
} else {
// 這里做不可重新連接的一些操作
console.log('連接不可用(可能需要密碼)')
}
this.rfb = null;
this.connectVnc();
},
// 連接成功的回調函數(shù)
connectedToServer() {
console.log('連接成功');
},
//連接vnc的函數(shù)
connectVnc() {
const PASSWORD = '';
let rfb = new RFB(document.getElementById('screen'), this.url, {
// 向vnc 傳遞的一些參數(shù),比如說虛擬機的開機密碼等
credentials: {password: PASSWORD}
});
rfb.addEventListener('connect', this.connectedToServer);
rfb.addEventListener('disconnect', this.disconnectedFromServer);
// scaleViewport指示是否應在本地擴展遠程會話以使其適合其容器。禁用時,如果遠程會話小于其容器,則它將居中,或者根據(jù)clipViewport它是否更大來處理。默認情況下禁用。
rfb.scaleViewport = true;
// 是一個boolean指示是否每當容器改變尺寸應被發(fā)送到調整遠程會話的請求。默認情況下禁用
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 轉發(fā)端口 */
const vnc_port = 8112;
/** 打印提示信息 */
console.log(`成功創(chuàng)建 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}, 協(xié)議 ${client.protocol}`);
/** 連接到 VNC Server */
const target = net.createConnection(port, host, function () {
console.log('連接至目標主機');
});
/** 數(shù)據(jù)事件 */
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句)配置允許創(chuàng)建websocket
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
}
4. 結果
圖4-1 運行結果截圖