no-vnc和node.js实现web远程桌面的完整步骤
引言
项目需求,要求在浏览器端进行远程桌面的访问,如图所示
实现远程桌面,需要依赖VNC协议
VNC(Virtual Network Computing),为一种使用RFB协议的屏幕画面分享及远程操作软件。此软件借由网络,可发送键盘与鼠标的动作及即时的屏幕画面。
相关的参考比较少,去谷歌搜索出来的文章大多都是如何使用客户端进行VNC的搭建与访问,很少有将其内嵌到web里的,腾讯云有相关的功能,但因为业务安全性,咱也看不着人家咋实现的。
再见,百度。用百度查了一次之后,我才知道原来VNC是口红。
所以VNC实践之路就是如下流程
- 根据自己已有的知识与技能,设计一个VNC方案。
- 尝试,分析可行性。
- 根据可行性修改方案细节,或推翻方案重新设计。
从整体的最开始设计,到最终落地方案,大约经历了以下七个方案的迭代
- SpringBoot调用REALVNC的C++类库,前后台进行数据交互。失败,因为REALVNC太贵了,客户承受不起。
- SpringBoot中模仿TightVNC实现JavaViewer获取数据,前后台进行数据交互。失败,因为TightVNC JavaViewer的源码没注释,看不懂。
- SpringBoot中手写VNC客户端,前后台数据交互。失败,因为从0实现一个协议太复杂了,时间成本太高。
- 浏览器端只做VNC链接,使用原生客户端,直接访问主机。失败,需要安装软件,且只能访问局域网中的主机。
- 原生客户端 + nginx数据转发。失败,需要安装软件,无法实现动态转发(无法动态变更nginx配置文件)。
- no-vnc + nginx数据转发。失败,无法实现动态转发(无法动态变更nginx配置文件)。
- no-vnc + node.js数据转发。成功,完美实现。
实现
思想
整体思想如下图所示nginx转发前台的websocket连接,为了实现外网转发,添加开发的node.js服务器作为代理,将浏览器端no-vnc的websocket数据报在运输层转发给目标主机。
why nginx ?
如果思考过的话,其实发现不用nginx也能实现功能,这里使用nginx主要是减少了前台对后台架构的耦合。
添加网关转发所有请求,对前台只暴露一个端口,不管后台用什么技术,用什么架构,用什么微服务,在前台看来,就好像在访问单体应用一样。
就像目前的华软项目一样,后台用了spring-boot、.、node.js,各语言各框架发挥各自的优势,通过nginx的转发将各模块连接起来,无论后台的架构怎么变,对前台毫无影响,这应该是微服务架构的最佳实践。
这是spring官方推荐的微服务架构图,我们学习并实践了api网关,spring推荐flix 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 RFB(this.container.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 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 转发
node.js监听8112端口,处理当前的websocket请求。
/ 建立基于 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); });
转发的核心代码在方法web_socket_handler中,以下是完整代码
这里说一句,之前写的注释都不规范,所有注释都应该是文档注释,单行注释使用/ 内容 /的格式。
/ 引入 http 包 / const http = require('http'); / 引入 包 / const = require(''); / 引入 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; / 截取主机地址 / 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 = .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(); }); };
为了这个功能犯愁了半个月,觉也睡不好,客户都在腾讯云上看到过的功能,写不出来就特别的难受,如今终于圆满解决。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对狼蚁SEO的支持。
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程