no-vnc和node.js實(shí)現(xiàn)web遠(yuǎn)程桌面的完整步驟
引言
項(xiàng)目需求,要求在瀏覽器端進(jìn)行遠(yuǎn)程桌面的訪問,如圖所示:

實(shí)現(xiàn)遠(yuǎn)程桌面,需要依賴VNC協(xié)議:
VNC(Virtual Network Computing),為一種使用RFB協(xié)議的屏幕畫面分享及遠(yuǎn)程操作軟件。此軟件借由網(wǎng)絡(luò),可發(fā)送鍵盤與鼠標(biāo)的動(dòng)作及即時(shí)的屏幕畫面。

相關(guān)的參考比較少,去谷歌搜索出來的文章大多都是如何使用客戶端進(jìn)行VNC的搭建與訪問,很少有將其內(nèi)嵌到web里的,騰訊云有相關(guān)的功能,但因?yàn)闃I(yè)務(wù)安全性,咱也看不著人家咋實(shí)現(xiàn)的。
再見,百度。用百度查了一次之后,我才知道原來VNC是口紅。

所以VNC實(shí)踐之路就是如下流程:
- 根據(jù)自己已有的知識(shí)與技能,設(shè)計(jì)一個(gè)VNC方案。
- 嘗試,分析可行性。
- 根據(jù)可行性修改方案細(xì)節(jié),或推翻方案重新設(shè)計(jì)。

從整體的最開始設(shè)計(jì),到最終落地方案,大約經(jīng)歷了以下七個(gè)方案的迭代:
- SpringBoot調(diào)用REALVNC的C++類庫,前后臺(tái)進(jìn)行數(shù)據(jù)交互。失敗,因?yàn)镽EALVNC太貴了,客戶承受不起。
- SpringBoot中模仿TightVNC實(shí)現(xiàn)JavaViewer獲取數(shù)據(jù),前后臺(tái)進(jìn)行數(shù)據(jù)交互。失敗,因?yàn)門ightVNC JavaViewer的源碼沒注釋,看不懂。
- SpringBoot中手寫VNC客戶端,前后臺(tái)數(shù)據(jù)交互。失敗,因?yàn)閺?實(shí)現(xiàn)一個(gè)協(xié)議太復(fù)雜了,時(shí)間成本太高。
- 瀏覽器端只做VNC鏈接,使用原生客戶端,直接訪問主機(jī)。失敗,需要安裝軟件,且只能訪問局域網(wǎng)中的主機(jī)。
- 原生客戶端 + nginx數(shù)據(jù)轉(zhuǎn)發(fā)。失敗,需要安裝軟件,無法實(shí)現(xiàn)動(dòng)態(tài)轉(zhuǎn)發(fā)(無法動(dòng)態(tài)變更nginx配置文件)。
- no-vnc + nginx數(shù)據(jù)轉(zhuǎn)發(fā)。失敗,無法實(shí)現(xiàn)動(dòng)態(tài)轉(zhuǎn)發(fā)(無法動(dòng)態(tài)變更nginx配置文件)。
- no-vnc + node.js數(shù)據(jù)轉(zhuǎn)發(fā)。成功,完美實(shí)現(xiàn)。
實(shí)現(xiàn)
思想
整體思想如下圖所示:nginx轉(zhuǎn)發(fā)前臺(tái)的websocket連接,為了實(shí)現(xiàn)外網(wǎng)轉(zhuǎn)發(fā),添加開發(fā)的node.js服務(wù)器作為代理,將瀏覽器端no-vnc的websocket數(shù)據(jù)報(bào)在運(yùn)輸層轉(zhuǎn)發(fā)給目標(biāo)主機(jī)。

why nginx ?
如果思考過的話,其實(shí)發(fā)現(xiàn)不用nginx也能實(shí)現(xiàn)功能,這里使用nginx主要是減少了前臺(tái)對后臺(tái)架構(gòu)的耦合。
添加網(wǎng)關(guān)轉(zhuǎn)發(fā)所有請求,對前臺(tái)只暴露一個(gè)端口,不管后臺(tái)用什么技術(shù),用什么架構(gòu),用什么微服務(wù),在前臺(tái)看來,就好像在訪問單體應(yīng)用一樣。
就像目前的華軟項(xiàng)目一樣,后臺(tái)用了spring-boot、.net、node.js,各語言各框架發(fā)揮各自的優(yōu)勢,通過nginx的轉(zhuǎn)發(fā)將各模塊連接起來,無論后臺(tái)的架構(gòu)怎么變,對前臺(tái)毫無影響,這應(yīng)該是微服務(wù)架構(gòu)的最佳實(shí)踐。

這是spring官方推薦的微服務(wù)架構(gòu)圖,我們學(xué)習(xí)并實(shí)踐了api網(wǎng)關(guān),spring推薦netflix zuul,我們用的nginx,在請求轉(zhuǎn)發(fā)上,二者性能不相上下。
隨著業(yè)務(wù)需求的增長,我們肯定也會(huì)服務(wù)拆分,服務(wù)注冊,服務(wù)發(fā)現(xiàn),消息隊(duì)列,RPC調(diào)用。然后用上eureka、zookeeper、hystrix、feign等一個(gè)個(gè)優(yōu)秀的開源組件,一起探索spring-cloud的最佳實(shí)踐。
websocket
之前一直不了解websocket,就是知道個(gè)名,具體細(xì)節(jié)沒有學(xué)習(xí)。
http協(xié)議:請求響應(yīng),客戶端請求,服務(wù)器響應(yīng),一次請求就結(jié)束。服務(wù)端無法主動(dòng)向客戶端推送數(shù)據(jù)。
為了解決這個(gè)問題,websocket應(yīng)運(yùn)而生。如果所示,不做贅述。

no-vnc
官網(wǎng)鏈接:noVNC

安裝依賴:
npm install @novnc/novnc
前臺(tái)組件
一個(gè)空div,同時(shí)在組件中引用。
<div class="container" #container> </div>
@ViewChild('container')
private container: ElementRef<HTMLDivElement>;
核心的代碼其實(shí)就這幾行,所有協(xié)議的細(xì)節(jié)都被封裝在no-vnc中的RFB類中了。
所有描述以訪問192.168.0.104主機(jī)的5900端口為例,websocket地址為:ws://127.0.0.1:8013/vnc/192.168.0.104:5900。
/**
* VNC連接
*/
private VNCConnect(): void {
/** 訪問 /vnc/ websocket */
const url = `ws://${this.host}/vnc/${this.ip}:${this.port}`;
/** 新建遠(yuǎn)程控制對象 */
this.rfb = new RFB(this.container.nativeElement, url, {
credentials: {
password: this.password,
},
});
/** 添加connect事件監(jiān)聽器 */
this.rfb.addEventListener('connect', () => {
this.rfb.focus();
});
}
nginx 轉(zhuǎn)發(fā)
nginx監(jiān)聽本地的8013端口。
ws://127.0.0.1:8013/vnc/192.168.0.104:5900請求發(fā)給了nginx,根據(jù)前綴匹配,以/vnc/開頭的轉(zhuǎn)發(fā)給8112端口。
location /vnc/ {
proxy_pass http://127.0.0.1:8112/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
node.js 轉(zhuǎn)發(fā)
node.js監(jiān)聽8112端口,處理當(dāng)前的websocket請求。
/** 建立基于 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);
});
轉(zhuǎn)發(fā)的核心代碼在方法web_socket_handler中,以下是完整代碼:
這里說一句,之前寫的注釋都不規(guī)范,所有注釋都應(yīng)該是文檔注釋,單行注釋使用/** 內(nèi)容 */的格式。
/** 引入 http 包 */
const http = require('http');
/** 引入 net 包 */
const net = require('net');
/** 引入 websocket 類 */
const WebSocketServer = require('ws').Server;
/** 本機(jī) ip 地址 */
const localhost = '127.0.0.1';
/** 開放的 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) {
/** 獲取請求url */
const url = req.url;
/** 截取主機(jī)地址 */
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('連接至目標(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 客戶端斷開連接:$[code] [${reason}]`);
target.end();
});
/** 錯(cuò)誤事件 */
client.on('error', function (error) {
console.log(`WebSocket 客戶端出錯(cuò):${error}`);
target.end();
});
};
總結(jié)
為了這個(gè)功能犯愁了半個(gè)月,覺也睡不好,客戶都在騰訊云上看到過的功能,寫不出來就特別的難受,如今終于圓滿解決。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
- 詳解node.js創(chuàng)建一個(gè)web服務(wù)器(Server)的詳細(xì)步驟
- Node.js web 應(yīng)用如何封裝到Docker容器中
- node.js 使用 net 模塊模擬 websocket 握手進(jìn)行數(shù)據(jù)傳遞操作示例
- 在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法
- node.js ws模塊搭建websocket服務(wù)端的方法示例
- node.js中ws模塊創(chuàng)建服務(wù)端和客戶端,網(wǎng)頁WebSocket客戶端
- Node.js原生api搭建web服務(wù)器的方法步驟
- 詳解如何使用node.js的開發(fā)框架express創(chuàng)建一個(gè)web應(yīng)用
- 使用NODE.JS創(chuàng)建一個(gè)WEBSERVER(服務(wù)器)的步驟
相關(guān)文章
Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置指南(簡單易操作)
在實(shí)際開發(fā)過程中我們可能需要安裝多個(gè)版本的 nodejs,下面這篇文章主要給大家介紹了關(guān)于Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
Node.js安裝詳細(xì)步驟教程(Windows版)詳解
這篇文章主要介紹了Node.js安裝詳細(xì)步驟教程(Windows版),本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
node錯(cuò)誤處理與日志記錄的實(shí)現(xiàn)
這篇文章主要介紹了node錯(cuò)誤處理與日志記錄的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12

