Node.js 实现远程桌面监控的方法步骤


Posted in Javascript onJuly 02, 2019

描述

最近使用node实现了一个远程桌面监控的应用,分为服务端和客户端,客户端可以实时监控服务端的桌面,并且可以通过鼠标和键盘来控制服务端的桌面。

Node.js 实现远程桌面监控的方法步骤

Node.js 实现远程桌面监控的方法步骤

这里因为我是用的同一台电脑,所以监控画面是这样的,当然使用两台电脑一个跑客户端,一个跑服务端才有意义。

原理

其实这个应用的功能主要分为两部分,一是实现监控,即在客户端可以看到服务端的桌面,这部分功能是通过定时截图来实现的,比如服务端一秒截几次图,然后通过socketio发送到客户端,客户端通过改变img的src来实现一帧帧的显示最新的图片,这样就能看到动态的桌面了。监控就是这样实现的。

另一个功能是控制,即客户端对监控画面的操作,包括鼠标和键盘的操作都可以在服务端的桌面真正的生效,这部分功能的实现是在electron的应用中监听了所有的鼠标和键盘事件,比如keydown、keyup、keypress,mousedown、mouseup、mousemove、click等,然后通过socketio把事件传递到服务端,服务端通过 robot-js来执行不同的事件,这样就能使得客户端的事件在服务端触发了。

实现

原理讲完,我们来具体实现一下(源码链接在这)。

实现socket通信

首先,服务端和客户端分别引入socket.io和socket.io-client, 分别初始化

服务端:

const app = new Koa();
const server = http.createServer(app.callback());
createSocketIO(server);

app.use((ctx): void => {
  ctx.body = 'please connect use socket';
});

server.listen(port, (): void => {
  console.log('server started at http://localhost:' + port);
});

//createSocketIO
const io = socketIO(server, {
    pingInterval: 10000,
    pingTimeout: 5000,
    cookie: false
  });

io.on('connect', (socket): void => {
  socket.emit('msg', 'connected');
}

客户端:

var socket = this.socket = io('http://' + this.ip + ':3000')
socket.on('msg', (msg) => {
 console.log(msg)
})
socket.on('error', (err) => {
 alert('出错了' + err)
})

这样,服务端和客户端就通过socketio建立了链接。

实现桌面监控

之后我们首先要在服务端来截图,使用screenshot-desktop这个包

const screenshot = require('screenshot-desktop')

const SCREENSHOT_INTERVAL = 500;

export const createScreenshot = (): Promise<[string, Buffer]> => {
  return screenshot({format: 'png'}).then((img): [string, Buffer] => {
    return [ img.toString('base64'), img];
  }).catch((err): {} => {
    console.log('截图失败', err);
    return err;
  })
}

export const startScreenshotTimer = (callback): {} => {
  return setInterval((): void => {
    createScreenshot().then(([imgStr, img]): void => {
      callback(['data:image/png;base64,' + imgStr, img]);
    })
  }, SCREENSHOT_INTERVAL)
}

然后通过socketio的emit来传到客户端:

startScreenshotTimer(([imgStr, img]): void => {
  io.sockets.emit('screenshot', imgStr);
});

客户端收到图片后,设置到img的src上(这里是base64的图片url):

<img 
  class="screenshot" 
  :src="screenshot"
/>
data () {
 return {
  screenshot: ''
 }
}
socket.on('screenshot', (data) => {
 this.screenshot = data
})

其实这样就已经实现了桌面监控了,有兴趣的同学可以照着这个思路实现看看,并不是很麻烦。

当然这样的方案是有问题的,因为我们需要知道服务端桌面尺寸的大小,然后根据这个来调整客户端显示的图片尺寸。

实现这个细节是使用的get-pixels这个库,可以读取本地图片文件的宽度高度等信息,所以我先把图片写入本地,然后又读取出来,这样获取到的屏幕尺寸。

interface ScreenSize {
  width: number;
  height: number;
}

function getScreenSize(img): Promise<ScreenSize> {
  const imgPath = path.resolve(process.cwd(), './tmp.png');
  fs.writeFileSync(imgPath, img);
  return new Promise((resolve): void => {
    getPixels(imgPath, function(err, pixels): void {
      if(err) {
        console.log("Bad image path")
        return
      }
      resolve({
        width: pixels.shape[0],
        height: pixels.shape[1]
      });
    });
  })
}

然后通过socektio传递给客户端

getScreenSize(img).then(({ width, height}) => {
  io.sockets.emit('screensize', {
    width,
    height
  })
});

客户端收到之后调整图片大小就可以了

<img 
  class="screenshot" 
  :src="screenshot"
  :style="screenshotStyle"
/>
data () {
 return {
  screenshot: '',
  screenshotStyle: '',
 }
}
socket.on('screensize', (screensize) => {
 this.screenshotStyle = {'width': screensize.width + 'px', 'height': screensize.height + 'px'}
})

至此已经实现了桌面监控,并且图片尺寸和服务端屏幕的尺寸是一致的。

这里还有一个细节,就是获取到的图片大小是物理像素,而客户端设置的px是设备无关像素,也就是要除以dpr才是px的值。这里需要获取dpr,因为目前只是在mac下用,所以直接除以2了。

实现远程控制

代码写到这里,客户端的electron应用中已经可以实时显示服务端的桌面了。(当然像输入ip的弹框,以及electron-vue和typescript等和主要逻辑无关的细节就不展开了。)

接下来我们要实现远程控制,也就是监听事件,传递事件,执行事件这几部分。

首先我们定义一下传递的事件的格式:

interface MouseEvent {
  type: string;
  buttonType: string;
  x: number;
  y: number;
}

interface KeyboardEvent {
  type: string;
  keyCode: number;
  keyName: string;
}

鼠标事件MouseEvent,type为鼠标事件的类型,具体的值包括mousedown、mouseup、mousemove、click、dblclick,buttonType指的是鼠标的左键还是右键,值为 left 或 right,x和y是具体的坐标。

键盘事件KeyboardEvent,type为键盘事件的类型,具体的值包括keydown、keyup、keypress,keyCode为键盘码,keyName为键的名字。

接下来我们要在客户端监听事件:

<img 
  class="screenshot" 
  :src="screenshot"
  :style="screenshotStyle"
  @mousedown="handleMouseEvent"
  @mousemove="handleMouseEvent" 
  @mouseup="handleMouseEvent"
  @click="handleMouseEvent"
  @dblclick="handleMouseEvent"  
/>
window.onkeypress = window.onkeyup = window.onkeydown = this.handleKeyboardEvent

通过socekt把事件传递到服务端

handleKeyboardEvent (e) {
  this.socket && this.socket.emit('userevent', {
   type: 'keyboard',
   event: {
    type: e.type,
    keyName: e.key,
    keyCode: e.keyCode
   }
  })
 },
 handleMouseEvent (e) {
  this.socket && this.socket.emit('userevent', {
   type: 'mouse',
   event: {
    type: e.type,
    buttonType: e.buttons === 2 ? 'right' : 'left',
    x: e.clientX,
    y: e.clientY
   }
  })
 },

然后在服务端把事件取出来执行,执行事件使用的是robot-js:

const { Mouse, Point, Keyboard } = require('robot-js');

interface MouseEvent {
  type: string;
  buttonType: string;
  x: number;
  y: number;
}

interface KeyboardEvent {
  type: string;
  keyCode: number;
  keyName: string;
}

export default class EventExecuter {
  public mouse;
  public keyboard;
  public constructor(){
    this.mouse = new Mouse();
    this.keyboard = new Keyboard();
  }

  public executeKeyboardEvent(event: KeyboardEvent): void {
    switch(event.type) {
      case 'keydown':
        this.keyboard.press(event.keyCode);
        break;
      case 'keyup':
        this.keyboard.release(event.keyCode);
        break;
      case 'keypress':
        this.keyboard.click(event.keyCode);
        break;
      default: break;
    }
  }

  public executeMouseEvent(event): void {
    Mouse.setPos(new Point(event.x, event.y));
    const button = event.buttonType === 'left' ? 0 : 2
    switch(event.type) {
      case 'mousedown':
        this.mouse.press(button);
        break;
      case 'mousemove':
        break;
      case 'mouseup': 
        this.mouse.release(button);
        break;
      case 'click': 
        this.mouse.click(button);
        break;
      case 'dblclick': 
        this.mouse.click(button);
        this.mouse.click(button);
        break;
      default: break;
    }
  }

  public exectue(eventInfo): void {
    console.log(eventInfo);
    switch (eventInfo.type) {
      case 'keyboard':
        this.executeKeyboardEvent(eventInfo.event);
        break;
      case 'mouse':
        this.executeMouseEvent(eventInfo.event);
        break;
      default: break;
    }
  }
}

至此,桌面监控和远程控制的客户端还有服务端的部分,以及两端的通信都已经实现了。思路其实并不麻烦,但细节还是很多的。有兴趣的同学可以把代码下下来跑跑试试,或者按着这个思路自己实现一遍,还是挺好玩的。

源码链接

remote-monitor-server

remote-monitor-client

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery新的事件绑定机制on()示例应用
Jul 18 Javascript
javascript删除元素节点removeChild()用法实例
May 26 Javascript
JS中处理时间之setUTCMinutes()方法的使用
Jun 12 Javascript
jQuery实现简单的点赞效果
May 29 Javascript
基于jQuery实现带动画效果超炫酷的弹出对话框(附源码下载)
Feb 22 Javascript
JS实现动态表格的添加,修改,删除功能(推荐)
Jun 15 Javascript
js严格模式总结(分享)
Aug 22 Javascript
javascript DOM的详解及实例代码
Mar 06 Javascript
JavaScript结合HTML DOM实现联动菜单
Apr 05 Javascript
详解使用Nuxt.js快速搭建服务端渲染(SSR)应用
Mar 13 Javascript
详解django模板与vue.js冲突问题
Jul 07 Javascript
Electron+vue从零开始打造一个本地播放器的方法示例
Oct 27 Javascript
使用vue中的混入mixin优化表单验证插件问题
Jul 02 #Javascript
vue history 模式打包部署在域名的二级目录的配置指南
Jul 02 #Javascript
简单了解微信小程序的目录结构
Jul 01 #Javascript
vue如何实现自定义底部菜单栏
Jul 01 #Javascript
微信小程序如何利用getCurrentPages进行页面传值
Jul 01 #Javascript
vue中的面包屑导航组件实例代码
Jul 01 #Javascript
Vue动态面包屑功能的实现方法
Jul 01 #Javascript
You might like
php使用memcoder将视频转成mp4格式的方法
2015/03/12 PHP
PHP写API输出的时用echo的原因详解
2019/04/28 PHP
基于Jquery 解决Ajax请求的页面 浏览器后退前进功能,页面刷新功能实效问题
2010/12/11 Javascript
合并table相同单元格的jquery插件分享(很精简)
2011/06/20 Javascript
Javascript中的delete介绍
2012/09/02 Javascript
前台js对象在后台转化java对象的问题探讨
2013/12/20 Javascript
js实现文本框中焦点在最后位置
2014/03/04 Javascript
javascript跨域原因以及解决方案分享
2015/04/08 Javascript
JavaScript中使用Math.PI圆周率属性的方法
2015/06/14 Javascript
js 求时间差的实现代码
2016/04/26 Javascript
值得分享和收藏的Bootstrap学习教程
2016/05/12 Javascript
基于javascript的异步编程实例详解
2017/04/10 Javascript
javascript实现电脑和手机版样式切换
2017/11/10 Javascript
JS运动改变单物体透明度的方法分析
2018/01/23 Javascript
jQuery滚动条美化插件nicescroll简单用法示例
2018/04/18 jQuery
还不懂递归?读完这篇文章保证你会懂
2018/07/29 Javascript
微信小程序实现拖拽功能
2019/09/26 Javascript
Vue实现剪切板图片压缩功能
2020/02/04 Javascript
[04:49]期待西雅图之战 2016国际邀请赛中国区预选赛WINGS战队赛后采访
2016/06/29 DOTA
Python多线程编程(四):使用Lock互斥锁
2015/04/05 Python
python 3.0 模拟用户登录功能并实现三次错误锁定
2017/11/01 Python
python逆向入门教程
2018/01/15 Python
python 自动批量打开网页的示例
2019/02/21 Python
python多进程读图提取特征存npy
2019/05/21 Python
pyinstaller打包单个exe后无法执行错误的解决方法
2019/06/21 Python
python实现图片二值化及灰度处理方式
2019/12/07 Python
Django+python服务器部署与环境部署教程详解
2020/03/30 Python
Origins加拿大官网:雅诗兰黛集团高端植物护肤品牌
2017/11/19 全球购物
医学生实习自我鉴定
2013/09/27 职场文书
文化活动实施方案
2014/03/28 职场文书
捐款活动总结
2014/08/27 职场文书
商场父亲节活动方案
2014/08/27 职场文书
庆六一宣传标语
2014/10/08 职场文书
战略性融资合作协议书范本
2014/10/17 职场文书
JavaScript流程控制(分支)
2021/12/06 Javascript
使用Django框架创建项目
2022/06/10 Python