当前位置: 首页 > 后端技术 > Node.js

no-vnc和node.js实现web远程桌面

时间:2023-04-03 14:57:52 Node.js

介绍项目需求需要在浏览器端访问远程桌面,如图:实现远程桌面需要依赖VNC协议:VNC(VirtualNetworkComputing),是一款使用RFB协议的屏幕共享和远程操作软件。本软件可以通过网络发送键盘鼠标操作和实时屏幕图像。相关参考文献相对较少。谷歌搜索的文章大多是如何使用客户端搭建和接入VNC。他们中很少有人将其嵌入到网络中。腾讯云有相关功能,但是因为业务安全,我们看不到别人是怎么实现的。再见,百度。百度了一下,才知道VNC就是口红。所以VNC的实践之路是这样的过程:根据自己已有的知识和技能,设计一个VNC方案。尝试并分析可行性。根据可行性修改方案的细节,或推翻方案重新设计。从整体的初步设计到最终的落地方案,经历了以下七次方案迭代:SpringBoot调用REALVNC的C++类库,前后台数据交互。失败是因为REALVNC太贵了,客户买不起。SpringBoot中仿TightVNC实现JavaViewer获取数据,前后台数据交互。失败,因为TightVNCJavaViewer的源码没有注释,所以看不懂。SpringBoot手写VNC客户端,前后台数据交互。失败了,因为从0实现一个协议太复杂,时间成本太高。浏览器只连接VNC,使用nativeclient直接访问host。失败,需要安装软件,只能访问局域网内的主机。原生客户端+nginx数据转发。失败,需要安装软件,不能实现动态转发(不能动态更改nginx配置文件)。no-vnc+nginx数据转发。失败,无法实现动态转发(无法动态更改nginx配置文件)。no-vnc+node.js数据转发。成功,完美实现。实现思路的整体思路如下图所示:nginx在前台转发websocket连接。为实现外网转发,添加开发节点。.为什么是nginx?仔细想想,其实发现不用nginx也能实现功能。这里使用nginx主要是为了降低前台与后台架构的耦合度。增加一个网关转发所有的请求,只暴露一个端口给前台。无论后台使用什么技术、架构、微服务,从前台的角度来看,都好像是在访问一个单体应用。就像现在的华软项目一样,后台使用了spring-boot、.net、node.js。每种语言和框架发挥自己的优势,通过nginx的转发将模块连接起来。无论后台结构如何变化,对前台都没有影响,这应该是微服务架构的最佳实践。这是spring官方推荐的微服务架构图。我们学习并实践了api网关。Spring推荐netflixzuul。我们使用nginx。在请求转发方面,两者性能不相上下。随着业务需求的增长,我们肯定还会拆分服务,服务注册,服务发现,消息队列,RPC调用。然后使用eureka、zookeeper、hystrix、feign等优秀的开源组件,一起探索spring-cloud的最佳实践。之前不知道websocket,只是知道名字,并没有深入了解。http协议:请求响应,客户端请求,服务器响应,一次请求结束。服务器不能主动向客户端推送数据。为了解决这个问题,websocket应运而生。如果显示,请不要重复。no-vnc官网链接:noVNC安装依赖:npminstall@novnc/novnc前端组件一个空的div,在组件中引用。

@ViewChild('container')私有容器:ElementRef;核心代码其实就这么几行,所有的协议细节都封装在RFB类的no-vnc中。所有描述均以访问192.168.0.104主机的5900端口为例,websocket地址为:ws://127.0.0.1:8013/vnc/192.168.0.104:5900。/***VNC连接*/privateVNCConnect():void{/**访问/vnc/websocket*/consturl=`ws://${this.host}/vnc/${this.ip}:${这个端口}`;/**新的远程控制对象*/this.rfb=newRFB(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_passhttp://127.0.0.1:8112/;proxy_http_version1.1;proxy_set_header升级$http_upgrade;proxy_set_headerConnection$connection_upgrade;}node.js转发node.js监听8112端口,处理当前的websocket请求。/**基于vnc_port建立websocket服务器*/constvnc_server=http.createServer();vnc_server.listen(vnc_port,function(){constweb_socket_server=newWebSocketServer({server:vnc_server});web_socket_server.on('connection',web_socket_handler);});转发的核心代码在方法web_socket_handler中,下面是完整代码:这里有一句话,之前写的注释不规范,所有注释都应该是文档注释,单行注释使用/**内容*/格式。/**导入http包*/consthttp=require('http');/**导入net包*/constnet=require('net');/**导入websocket类*/constWebSocketServer=require('ws').Server;/**本地ip地址*/constlocalhost='127.0.0.1';/**开启vncwebsocket转发端口*/constvnc_port='8112';/**打印提示信息*/console.log(`成功创建WebSocket代理:${localhost}:${vnc_port}`);/**基于vnc_port建立websocket服务器*/constvnc_server=http.createServer();vnc_server.listen(vnc_port,function(){constweb_socket_server=newWebSocketServer({server:vnc_server});web_socket_server.on('connection',web_socket_handler);});/**websockethandler*/constweb_socket_handler=function(client,req){/**获取请求url*/consturl=req.url;/**拦截主机地址*/consthost=url.substring(url.indexOf('/')+1,url.indexOf(':'));/**拦截端口号*/constport=Number(url.substring(url.indexOf(':')+1));/**打印日志*/console.log(`WebSocket连接:版本${client.protocolVersion},协议${client.protocol}`);/**连接到VNC服务器*/consttarget=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('消息',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(`WebSocketClienterror:${error}`);target.end();});};总结就是担心这个功能半个月后,我无法入睡。客户已经看到了腾讯云上的功能。如果他们不能写,那会很不舒服。现在终于解决了。拥抱开源,互相帮助。如果我能通过这个博客看到我的过去,我就不会浪费这么长时间一次又一次地尝试。