no-vnc和node.js实现web远程桌面的完整步骤


Posted in Javascript onAugust 11, 2019

引言

项目需求,要求在浏览器端进行远程桌面的访问,如图所示:

no-vnc和node.js实现web远程桌面的完整步骤

实现远程桌面,需要依赖VNC协议:

VNC(Virtual Network Computing),为一种使用RFB协议的屏幕画面分享及远程操作软件。此软件借由网络,可发送键盘与鼠标的动作及即时的屏幕画面。

no-vnc和node.js实现web远程桌面的完整步骤

相关的参考比较少,去谷歌搜索出来的文章大多都是如何使用客户端进行VNC的搭建与访问,很少有将其内嵌到web里的,腾讯云有相关的功能,但因为业务安全性,咱也看不着人家咋实现的。

再见,百度。用百度查了一次之后,我才知道原来VNC是口红。

no-vnc和node.js实现web远程桌面的完整步骤

所以VNC实践之路就是如下流程:

  1. 根据自己已有的知识与技能,设计一个VNC方案。
  2. 尝试,分析可行性。
  3. 根据可行性修改方案细节,或推翻方案重新设计。

no-vnc和node.js实现web远程桌面的完整步骤

从整体的最开始设计,到最终落地方案,大约经历了以下七个方案的迭代:

  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数据报在运输层转发给目标主机。

no-vnc和node.js实现web远程桌面的完整步骤

why nginx ?

如果思考过的话,其实发现不用nginx也能实现功能,这里使用nginx主要是减少了前台对后台架构的耦合。

添加网关转发所有请求,对前台只暴露一个端口,不管后台用什么技术,用什么架构,用什么微服务,在前台看来,就好像在访问单体应用一样。

就像目前的华软项目一样,后台用了spring-boot、.net、node.js,各语言各框架发挥各自的优势,通过nginx的转发将各模块连接起来,无论后台的架构怎么变,对前台毫无影响,这应该是微服务架构的最佳实践。

no-vnc和node.js实现web远程桌面的完整步骤

这是spring官方推荐的微服务架构图,我们学习并实践了api网关,spring推荐netflix zuul,我们用的nginx,在请求转发上,二者性能不相上下。

随着业务需求的增长,我们肯定也会服务拆分,服务注册,服务发现,消息队列,RPC调用。然后用上eureka、zookeeper、hystrix、feign等一个个优秀的开源组件,一起探索spring-cloud的最佳实践。

websocket

之前一直不了解websocket,就是知道个名,具体细节没有学习。

http协议:请求响应,客户端请求,服务器响应,一次请求就结束。服务端无法主动向客户端推送数据。

为了解决这个问题,websocket应运而生。如果所示,不做赘述。

no-vnc和node.js实现web远程桌面的完整步骤

no-vnc

官网链接:noVNC

no-vnc和node.js实现web远程桌面的完整步骤

安装依赖:

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');

/** 引入 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 = 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 = 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('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();
  });
};

总结

为了这个功能犯愁了半个月,觉也睡不好,客户都在腾讯云上看到过的功能,写不出来就特别的难受,如今终于圆满解决。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
c#和Javascript操作同一json对象的实现代码
Jan 17 Javascript
JS操作图片(增,删,改) 例子
Apr 17 Javascript
js页面跳转的问题(跳转到父页面、最外层页面、本页面)
Aug 14 Javascript
JavaScript的strict模式与with关键字介绍
Feb 08 Javascript
通过隐藏iframe实现文件下载的js方法介绍
Feb 26 Javascript
基于jquery的手风琴图片展示效果实现方法
Dec 16 Javascript
轻松学习jQuery插件EasyUI EasyUI实现拖动基本操作
Nov 30 Javascript
jQuery旋转木马式幻灯片轮播特效
Dec 04 Javascript
原生js实现放大镜效果
Jan 11 Javascript
vue父组件向子组件传递多个数据的实例
Mar 01 Javascript
详解使用jest对vue项目进行单元测试
Sep 07 Javascript
vue使用swiper实现中间大两边小的轮播图效果
Nov 24 Javascript
Angular8基础应用之表单及其验证
Aug 11 #Javascript
浅谈javascript错误处理
Aug 11 #Javascript
axios异步提交表单数据的几种方法
Aug 11 #Javascript
node.js实现带进度条的多文件上传
Mar 27 #Javascript
基于Express框架使用POST传递Form数据
Aug 10 #Javascript
Vue实现点击显示不同图片的效果
Aug 10 #Javascript
vue+eslint+vscode配置教程
Aug 09 #Javascript
You might like
PHP 已经成熟
2006/12/04 PHP
Smarty缓存机制实例详解【三种缓存方式】
2019/07/20 PHP
js的隐含参数(arguments,callee,caller)使用方法
2014/01/28 Javascript
js实现jquery的offset()方法实例
2015/01/10 Javascript
Javascript控制input输入时间格式的方法
2015/01/28 Javascript
Vue.JS入门教程之处理表单
2016/12/01 Javascript
JavaScript拖动层Div代码
2017/03/01 Javascript
详解Vue.js 2.0 如何使用axios
2017/04/21 Javascript
关于Angularjs中自定义指令一些有价值的细节和技巧小结
2018/04/22 Javascript
详解Axios 如何取消已发送的请求
2018/10/20 Javascript
Vue.js样式动态绑定实现小结
2019/01/24 Javascript
[02:36]DOTA2英雄基础教程 斯拉克
2013/11/29 DOTA
Python中多线程及程序锁浅析
2015/01/21 Python
在Python中处理字符串之isdecimal()方法的使用
2015/05/20 Python
Python中如何使用if语句处理列表实例代码
2019/02/24 Python
python使用MQTT给硬件传输图片的实现方法
2019/05/05 Python
用scikit-learn和pandas学习线性回归的方法
2019/06/21 Python
通过python改变图片特定区域的颜色详解
2019/07/15 Python
python json.dumps中文乱码问题解决
2020/04/01 Python
美国嘻哈首饰购物网站:Hip Hop Bling
2016/12/30 全球购物
捷克电器和DJ设备网上商店:Electronic-star
2017/07/18 全球购物
英国和世界各地预订便宜的酒店:LateRooms.com
2019/05/05 全球购物
世界上最受欢迎的花店:1-800-Flowers.com
2020/06/01 全球购物
商务英语毕业生自荐信范文
2013/11/08 职场文书
家长给孩子的评语
2014/01/30 职场文书
职业培训师职业生涯规划
2014/02/18 职场文书
《雷雨》教学反思
2014/02/20 职场文书
大学生学习2014全国两会心得体会
2014/03/13 职场文书
元旦晚会主持词
2014/03/24 职场文书
食品安全宣传标语
2014/06/07 职场文书
幼儿园教师安全责任书
2015/05/08 职场文书
2015年财务个人工作总结范文
2015/05/22 职场文书
预备党员半年考察意见
2015/06/01 职场文书
二胎满月酒致辞
2015/07/29 职场文书
python开发实时可视化仪表盘的示例
2021/05/07 Python
使用python绘制横竖条形图
2022/04/21 Python