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 相关文章推荐
JScript内置对象Array中元素的删除方法
Mar 08 Javascript
使用js操作css实现js改变背景图片示例
Mar 10 Javascript
node.js中的http.response.writeHead方法使用说明
Dec 14 Javascript
jQuery中insertBefore()方法用法实例
Jan 08 Javascript
JS中Eval解析JSON字符串的一个小问题
Feb 21 Javascript
jQuery插件MovingBoxes实现左右滑动中间放大图片效果
Feb 28 Javascript
js获取json中key所对应的value值的简单方法
Jun 17 Javascript
JS按钮闪烁功能的实现代码
Jul 21 Javascript
js实现input密码框显示/隐藏功能
Sep 10 Javascript
小程序实现订单倒计时功能
Apr 23 Javascript
浅谈Vue.js之初始化el以及数据的绑定说明
Nov 14 Javascript
JavaScript实现鼠标经过表格某行时此行变色
Nov 20 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
DC动画电影《黑暗正义联盟》曝预告 5月5日上线数字平台
2020/04/09 欧美动漫
PHP 进程锁定问题分析研究
2009/11/24 PHP
php下封装较好的数字分页方法
2010/11/23 PHP
ThinkPHP中的关联模型注意点
2014/06/16 PHP
利用PHP绘图函数实现简单验证码功能的方法
2016/10/18 PHP
微信公众号开发之获取位置信息php代码
2018/06/13 PHP
详解PHP素材图片上传、下载功能
2019/04/12 PHP
JQueryEasyUI Layout布局框架的使用
2013/04/08 Javascript
比较不错的JS/JQuery显示或隐藏文本的方法
2014/02/13 Javascript
JS+DIV+CSS实现的经典标签切换效果代码
2015/09/14 Javascript
jQuery.ajax 跨域请求webapi设置headers的解决方案
2016/08/08 Javascript
js实现简单的计算器功能
2017/01/16 Javascript
Node学习记录之cluster模块
2017/05/31 Javascript
Javascript网页抢红包外挂实现分享
2018/01/11 Javascript
spirngmvc js传递复杂json参数到controller的实例
2018/03/29 Javascript
jQuery仿移动端支付宝键盘的实现代码
2018/08/15 jQuery
详解javascript函数写法大全
2019/03/25 Javascript
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
Vue实现兄弟组件间的联动效果
2020/01/21 Javascript
js实现限定区域范围拖拉拽效果
2020/11/20 Javascript
Python标准库内置函数complex介绍
2014/11/25 Python
基于python 爬虫爬到含空格的url的处理方法
2018/05/11 Python
在Python 中实现图片加框和加字的方法
2019/01/26 Python
Python实现的微信红包提醒功能示例
2019/08/22 Python
python异步Web框架sanic的实现
2020/04/27 Python
python开根号实例讲解
2020/08/30 Python
Python+OpenCV图像处理——实现直线检测
2020/10/23 Python
世嘉游戏英国官方商店:SEGA Shop UK
2019/09/20 全球购物
总经理司机职责
2014/02/02 职场文书
选秀节目策划方案
2014/06/06 职场文书
群众路线四风自我剖析材料
2014/10/08 职场文书
银行党员批评与自我批评
2014/10/15 职场文书
工作经验交流材料
2014/12/30 职场文书
周一问候语大全
2015/11/10 职场文书
python3美化表格数据输出结果的实现代码
2021/04/14 Python
浅析Python中的套接字编程
2021/06/22 Python