no-vnc和node.js实现web远程桌⾯的完整步骤
引⾔
项⽬需求,要求在浏览器端进⾏远程桌⾯的访问,如图所⽰:
实现远程桌⾯,需要依赖VNC协议:
VNC(Virtual Network Computing),为⼀种使⽤RFB协议的屏幕画⾯分享及远程操作软件。此软件借由⽹络,可发送键盘与⿏标的动作及即时的屏幕画⾯。
相关的参考⽐较少,去⾕歌搜索出来的⽂章⼤多都是如何使⽤客户端进⾏VNC的搭建与访问,很少有将其内嵌到web⾥的,腾讯云有相关的功能,但因为业务安全性,咱也看不着⼈家咋实现的。
再见,百度。⽤百度查了⼀次之后,我才知道原来VNC是⼝红。
所以VNC实践之路就是如下流程:
1. 根据⾃⼰已有的知识与技能,设计⼀个VNC⽅案。
2. 尝试,分析可⾏性。
3. 根据可⾏性修改⽅案细节,或推翻⽅案重新设计。
从整体的最开始设计,到最终落地⽅案,⼤约经历了以下七个⽅案的迭代:
1. SpringBoot调⽤REALVNC的C++类库,前后台进⾏数据交互。失败,因为REALVNC太贵了,客户承受不起。
2. SpringBoot中模仿TightVNC实现JavaViewer获取数据,前后台进⾏数据交互。失败,因为TightVNC JavaViewer的源
码没注释,看不懂。
3. SpringBoot中⼿写VNC客户端,前后台数据交互。失败,因为从0实现⼀个协议太复杂了,时间成本太⾼。
4. 浏览器端只做VNC链接,使⽤原⽣客户端,直接访问主机。失败,需要安装软件,且只能访问局域⽹中的主机。
5. 原⽣客户端 + nginx数据转发。失败,需要安装软件,⽆法实现动态转发(⽆法动态变更nginx配置⽂件)。
6. no-vnc + nginx数据转发。失败,⽆法实现动态转发(⽆法动态变更nginx配置⽂件)。
7. no-vnc + node.js数据转发。成功,完美实现。
实现
思想
整体思想如下图所⽰:nginx转发前台的websocket连接,为了实现外⽹转发,添加开发的node.js服务器作为代理,将浏览器端no-vnc的websocket数据报在运输层转发给⽬标主机。
why nginx ?
如果思考过的话,其实发现不⽤nginx也能实现功能,这⾥使⽤nginx主要是减少了前台对后台架构的耦合。前端websocket怎么用
添加⽹关转发所有请求,对前台只暴露⼀个端⼝,不管后台⽤什么技术,⽤什么架构,⽤什么微服务,在前台看来,就好像在访问单体应⽤⼀样。
就像⽬前的华软项⽬⼀样,后台⽤了spring-boot、、node.js,各语⾔各框架发挥各⾃的优势,通过n
ginx的转发将各模块连接起来,⽆论后台的架构怎么变,对前台毫⽆影响,这应该是微服务架构的最佳实践。
这是spring官⽅推荐的微服务架构图,我们学习并实践了api⽹关,spring推荐netflix zuul,我们⽤的nginx,在请求转发上,⼆者性能不相上下。
随着业务需求的增长,我们肯定也会服务拆分,服务注册,服务发现,消息队列,RPC调⽤。然后⽤上eureka、zookeeper、hystrix、feign等⼀个个优秀的开源组件,⼀起探索spring-cloud的最佳实践。
websocket
之前⼀直不了解websocket,就是知道个名,具体细节没有学习。
http协议:请求响应,客户端请求,服务器响应,⼀次请求就结束。服务端⽆法主动向客户端推送数据。
为了解决这个问题,websocket应运⽽⽣。如果所⽰,不做赘述。
no-vnc
官⽹链接:
安装依赖:
npm install @novnc/novnc
前台组件
⼀个空div,同时在组件中引⽤。
<div class="container" #container>
</div>
@ViewChild('container')
private container: ElementRef<HTMLDivElement>;
核⼼的代码其实就这⼏⾏,所有协议的细节都被封装在no-vnc中的RFB类中了。
所有描述以访问192.168.0.104主机的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}`;
/** 新建远程控制对象 */
this.rfb = new ainer.nativeElement, url, {
credentials: {
password: this.password,
},
});
/** 添加connect事件 */
this.rfb.addEventListener('connect', () => {
this.rfb.focus();
});
}
nginx 转发
nginx监听本地的8013端⼝。
ws://127.0.0.1:8013/vnc/192.168.0.104:5900请求发给了nginx,根据前缀匹配,以/vnc/开头的转发给8112端⼝。location /vnc/ {
proxy_pass 127.0.0.1:8112/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
node.js 转发
node.js监听8112端⼝,处理当前的websocket请求。
/** 建⽴基于 vnc_port 的 websocket 服务器 */
const vnc_server = ateServer();
vnc_server.listen(vnc_port, function () {
const web_socket_server = new WebSocketServer({server: vnc_server});
web_('connection', web_socket_handler);
});
转发的核⼼代码在⽅法web_socket_handler中,以下是完整代码:
这⾥说⼀句,之前写的注释都不规范,所有注释都应该是⽂档注释,单⾏注释使⽤/** 内容 */的格式。/** 引⼊ 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 = ateServer();
vnc_server.listen(vnc_port, function () {
const web_socket_server = new WebSocketServer({server: vnc_server});
web_('connection', web_socket_handler);
});
/** websocket 处理器 */
const web_socket_handler = function (client, req) {
/** 获取请求url */
const url = req.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 = ateConnection(port, host, function () {
console.log('连接⾄⽬标主机');
});
/** 数据事件 */
<('data', function (data) {
try {
client.send(data);
} catch (error) {
console.log('客户端已关闭,清理到⽬标主机的连接');
}
});
/** 结束事件 */
<('end', function () {
console.log('⽬标主机已关闭');
client.close();
});
/** 错误事件 */
<('error', function () {
console.log('⽬标主机连接错误');
client.close();
});
/** 消息事件 */
<('message', function (msg) {
target.write(msg);
});
/
** 关闭事件 */
<('close', function (code, reason) {
console.log(`WebSocket 客户端断开连接:$[code] [${reason}]`);
});
/** 错误事件 */
<('error', function (error) {
console.log(`WebSocket 客户端出错:${error}`);
});
};
总结
为了这个功能犯愁了半个⽉,觉也睡不好,客户都在腾讯云上看到过的功能,写不出来就特别的难受,如今终于圆满解决。
好了,以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论