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 相关文章推荐
COM中获取JavaScript数组大小的代码
Nov 22 Javascript
jQuery UI AutoComplete 自动完成使用小记
Aug 21 Javascript
基于JQuery的抓取博客园首页RSS的代码
Dec 01 Javascript
jQuery学习笔记之创建DOM元素
Jan 19 Javascript
Javascript 高阶函数使用介绍
Jun 15 Javascript
JS实现黑客帝国文字下落效果
Sep 01 Javascript
AngularJS 实现JavaScript 动画效果详解
Sep 08 Javascript
AngularJS删除路由中的#符号的方法
Sep 20 Javascript
javascript轮播图算法
Oct 21 Javascript
Vue2.0 UI框架ElementUI使用方法详解
Apr 14 Javascript
JavaScript直接调用函数与call调用的区别实例分析
May 22 Javascript
node.js如何自定义实现一个EventEmitter
Jul 16 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 快速生成 Flash 动画的方法
2007/03/06 PHP
纯PHP生成的一个树叶图片画图例子
2014/04/16 PHP
在Mac OS上编译安装Nginx+PHP+MariaDB开发环境的教程
2016/02/23 PHP
Yii框架通过请求组件处理get,post请求的方法分析
2019/09/03 PHP
Javascript表达式中连续的 &amp;&amp; 和 || 之赋值区别
2010/10/17 Javascript
理解Javascript_14_函数形式参数与arguments
2010/10/20 Javascript
jquery图片放大镜功能的实例代码
2013/03/26 Javascript
让元素在网页中可拖动示例代码
2013/08/13 Javascript
NodeJS中Buffer模块详解
2015/01/07 NodeJs
jquery实现九宫格大转盘抽奖
2015/11/13 Javascript
js实现图片旋转 js滚动鼠标中间对图片放大缩小
2017/07/05 Javascript
gulp教程_从入门到项目中快速上手使用方法
2017/09/14 Javascript
vue使用技巧及vue项目中遇到的问题
2018/06/04 Javascript
vue3.0 CLI - 3.2 路由的初级使用教程
2018/09/20 Javascript
element上传组件循环引用及简单时间倒计时的实现
2018/10/01 Javascript
ng-zorro-antd 入门初体验
2018/12/03 Javascript
elementUI 动态生成几行几列的方法示例
2019/07/11 Javascript
基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能
2019/11/12 Javascript
react 生命周期实例分析
2020/05/18 Javascript
[02:03]风行者至宝清风环佩外观展示
2020/09/05 DOTA
python实现简单的socket server实例
2015/04/29 Python
python字符串string的内置方法实例详解
2018/05/14 Python
Django中使用Celery的教程详解
2018/08/24 Python
python3+requests接口自动化session操作方法
2018/10/13 Python
python提取包含关键字的整行数据方法
2018/12/11 Python
Python3实现统计单词表中每个字母出现频率的方法示例
2019/01/28 Python
django框架使用orm实现批量更新数据的方法
2019/06/21 Python
django使用admin站点上传图片的实例
2019/07/28 Python
Django中使用session保持用户登陆连接的例子
2019/08/06 Python
canvas实现扭蛋机动画效果的示例代码
2018/10/17 HTML / CSS
DELPHI中如何调用API,可举例说明
2014/01/16 面试题
童装店创业计划书
2014/01/09 职场文书
黄河的主人教学反思
2014/02/07 职场文书
初中政教处工作总结
2015/08/12 职场文书
vue+springboot实现登录验证码
2021/05/27 Vue.js
PHP设计模式(观察者模式)
2021/07/07 PHP