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 CHECKBOX全选,取消全选,反选方法三
Aug 30 Javascript
js写一个弹出层并锁屏效果实现代码
Dec 07 Javascript
JS实现图片产生波纹一样flash效果的方法
Feb 27 Javascript
jQuery实现渐变下拉菜单的简单方法
Mar 11 Javascript
javascript中DOM复选框选择用法实例
May 14 Javascript
jQuery页面刷新(局部、全部)问题分析
Jan 09 Javascript
js与jQuery实现的用户注册协议倒计时功能实例【三种方法】
Nov 09 jQuery
简单了解node npm cnpm的具体使用方法
Feb 27 Javascript
关于element-ui的隐藏组件el-scrollbar的使用
May 29 Javascript
layui 数据表格 根据值(1=业务,2=机构)显示中文名称示例
Oct 26 Javascript
React实现全选功能
Aug 25 Javascript
Ajax异步刷新功能及简单案例
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
咖啡历史、消费和行业趋势
2021/03/03 咖啡文化
PHPMailer发送HTML内容、带附件的邮件实例
2014/07/01 PHP
codeigniter显示所有脚本执行时间的方法
2015/03/21 PHP
详解WordPress中添加友情链接的方法
2016/05/21 PHP
PHP让网站移动访问更加友好方法
2019/02/14 PHP
PHP调用接口API封装的例子
2019/10/11 PHP
PHP pthreads v3下的Volatile简介与使用方法示例
2020/02/21 PHP
Jquery iframe内部出滚动条
2010/02/11 Javascript
JavaScript 封装一个tab效果源码分享
2015/09/15 Javascript
JS判断是否长按某一键的方法
2016/03/02 Javascript
Javascript自执行匿名函数(function() { })()的原理浅析
2016/05/15 Javascript
js严格模式总结(分享)
2016/08/22 Javascript
微信小程序 Audio API详解及实例代码
2016/09/30 Javascript
JS实现表单多文件上传样式美化支持选中文件后删除相关项
2016/09/30 Javascript
vue左右侧联动滚动的实现代码
2018/06/06 Javascript
详解JavaScript 新语法之Class 的私有属性与私有方法
2019/04/23 Javascript
Python中多线程及程序锁浅析
2015/01/21 Python
Python多线程编程(五):死锁的形成
2015/04/05 Python
python字符串对其居中显示的方法
2015/07/11 Python
Python中time模块与datetime模块在使用中的不同之处
2015/11/24 Python
黑科技 Python脚本帮你找出微信上删除你好友的人
2016/01/07 Python
利用Python实现Windows下的鼠标键盘模拟的实例代码
2017/07/13 Python
简单实现python进度条脚本
2017/12/18 Python
python爬取淘宝商品销量信息
2018/11/16 Python
python turtle库画一个方格和圆实例
2019/06/27 Python
OpenCV 轮廓检测的实现方法
2019/07/03 Python
python通过TimedRotatingFileHandler按时间切割日志
2019/07/17 Python
解决django中form表单设置action后无法回到原页面的问题
2020/03/13 Python
Python Selenium 设置元素等待的三种方式
2020/03/18 Python
python使用建议技巧分享(三)
2020/08/18 Python
美国女性运动零售品牌:Lady Foot Locker
2017/05/12 全球购物
影视制作岗位职责
2013/12/04 职场文书
《蒲公英》教学反思
2014/02/28 职场文书
县长“四风”对照检查材料思想汇报
2014/10/05 职场文书
党员查摆问题及整改措施
2014/10/10 职场文书
python之PySide2安装使用及QT Designer UI设计案例教程
2021/07/26 Python