使用noVnc远程控制桌面后自动打开应用程序

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 使用noVnc远程控制桌面后自动打开应用程序

项目描述



该项目的需求是当我们使用noVnc远程连接另一台电脑后,能打开一款名叫tecplot的软件。


该项目时序图



image.png

项目实现思路



1.成功连接noVnc后,修改noVnc里面的源代码。采用websocket进行客户端和服务器之间的通讯

2.在websocket serve端采用父子进程的方式打开应用程序

3.使用log4j进行项目日志的管理

4.用pm2来启动node服务端项目


客户端实现



noVnc的使用和安装大家可以看一下这篇博客 All in Web | 基于web的远程桌面-noVNC - 知乎 (zhihu.com)

修改noVnc.html源码,在连接成功之后执行websocket的连接,发送消息


const CMD_CODE = {
    "OPEN":0,
    "CLOSE":1,
}
function NoVncClient(IP,urlFile) {
    this.pid = null;
    this.wsClient = null;
    this.IP = IP;
    this.fileUrl = urlFile;
    this.connect = connect;
    this.onMessage = receiveMsg;
    this.disConnect = disConnect
}
function connect() {
    console.log('this con', this)
    this.wsClient = new WebSocket(`ws://${this.IP}:8080`);
    const url = this.fileUrl;
    this.wsClient.onopen = function () {
        console.log(this.readyState,'连接成功')
        const sendMsg = {
            cmd:CMD_CODE.OPEN,
            url:url,
        }
        console.log('sendMsg',sendMsg)
        this.send(JSON.stringify(sendMsg))
    }
    this.wsClient.addEventListener('error', function (event) {
        console.log('WebSocket error: ', event);
    });
}
function receiveMsg() {
    const that = this;
    this.wsClient.onmessage = function (msg) {
        const receiveFromServerMsg = JSON.parse(msg.data)
        // UI.showStatus(receiveFromServerMsg, 'normal');
        console.log('remote msg',receiveFromServerMsg)
        that.pid = receiveFromServerMsg.pid
    }
}
function disConnect() {
    console.log(this.pid,this,'this dis')
    const closeMsg = {
        pid:this.pid,
        cmd:CMD_CODE.CLOSE,
        url:this.fileUrl
    }
    console.log('close',closeMsg)
    // UI.showStatus(closeMsg, 'normal');
    this.wsClient.send(JSON.stringify(closeMsg))
}
function getUrlParamsByName(name) {
    let reg = new RegExp(`(?<=\b${name}=)[^&]*`),
        str = location.search || '',
        target = str.match(reg);
    if(target) {
        return target[0]
    }
}
const IP=document.domain;
const noClient = new NoVncClient(IP,getUrlParamsByName('path'));
noClient.connect()
noClient.onMessage()
window.onbeforeunload = function(){
    noClient.disConnect()
}


服务端实现



创建websocket连接


const ws =new WebSocketServer({
    port:PORT
})


收到客户端的消息


ws.on('connection', function(ws){
    recLog.send({msg:'websocket connect success'})
    ws.on('message',function(message){
        console.log('父进程收到客户端的消息',JSON.parse(message))
        receiveMsgFromClient(message);
    })
    setInterval(() =>transmitMsg(ws),10000)
})


向客户端发送消息


// 这里的ws是connection回调中的ws,而不是创建的ws实例.
// 不同客户端的连接对应一个不同的ws
ws.send(JSON.stringify(msg))


采用子进程启动应用程序


const parentInstance = ()=> {
    const parent = spawn("node", ["child.js"], {
        cwd: path.resolve(__dirname, './child'),
        stdio: [0, 1, 2, "ipc"]
    })
    return parent;
}
- options
    - cwd:Current working directory of the child process.子进程当前的工作目录
    - stdio: ['inherit', 'inherit', 'inherit','ipc']使用ipc的方法进行通信


父进程转发消息给客户端和子进程


const transmitMsg = (ws)=>{
    const parent = parentInstance()
    if(messageQue.length){
        const msg = messageQue.shift()
        console.log('父进程发送给子进程消息')
        parent.send(msg)
    }
    parent.on("message",(data)=>{
        const {code} = data;
        console.log('父进程收到子进程消息')
        const recLog = log4jInstance()
        const sendVal = JSON.stringify(data);
        switch (code) {
            case PROCESS_CODE.RUNNING:
                childProcessNum++;
                recLog.send({msg:sendVal})
                ws.send(sendVal)
                break;
            case PROCESS_CODE.EXIT:
                childProcessNum--;
                ws.send(sendVal)
                recLog.send({msg:sendVal})
                break;
            case PROCESS_CODE.ERROR:
                ws.send(sendVal)
                recLog.send({msg:sendVal})
                break;
            default:
                break;
        }
    })
}


子进程接受,发送消息


process代表我当前的子进程


process.on('message', (data) => {
    console.log(data)
})
process.send(msg)
process.on("message",(data)=>{
    const {cmd,url} = data;
    const recLog = log4jInstance()
    switch (cmd) {
        case CMD_CODE.OPEN:
            execFilePromise(url).then((value)=>{
                console.log('子进程发送给父进程')
                process.send(value)
                recLog.send({msg:value})
            }).catch((err)=>{
                process.send(err)
                recLog.send({msg:err,type:'error'})
            })
            break;
        case CMD_CODE.EXIT:
            const { pid } = data;
            console.log('kill',pid)
            kill(pid,(err)=>{
                if(err){
                    process.send({msg:err,code:PROCESS_CODE.ERROR})
                    recLog.send({msg:err,type:'error'})
                }
                else{
                    process.send({msg:"close success",code:PROCESS_CODE.EXIT})
                    recLog.send({msg:`kill ${pid} success`})
                }
            })
            break
        default:
            console.log('default');
            break;
    }
})


log4j日志管理



log4j配置文件


log4js.configure({
  appenders: { cheese: { type: "file", filename: "cheese.log" } },
  categories: { default: { appenders: ["cheese"], level: "error" } },
});


log4j的配置主要分成两个部分,分别是appenders和categories


appenders


appenders配置的是每一种的输出方式,你可以在appenders配置多种输出方式


out: {
    type: 'stdout',
},
dataFile: {
    type: 'dateFile',
    numBackups:7,//7天之前的文件即删除
    filename: '../logs/out.log',
    keepFileExt: true,
    maxLogSize: 20480000,
    backups: 4,
},


比如我这里配置了两种方式分别采用了stdout控制台输出和dataFile文件输出


categories


categories表示你选择你在appenders里面配置的那种输出方式输出


categories: {
    default:{
        appenders:['out','dataFile'],
        level:'debug', // 输出等级,只输出比这个等级高的
    },
},


这里我采用我在appenders里面配置的两种输出方式,即会输出到控制台和out.log这个文件中


实例化logger


// 当我在getLogger()里面不传任何参数的时候,默认对应categories里面default配置
// default配置再会根据我里面的appenders数组,去找appender中out和dataFile的配置
let logger = log4js.getLogger();


我们还可以实例出不同的logger,分别输出不同类型的文件


categories: {
    default: {
      appenders: ['consoleOut', 'default'],
      level: 'all',
    },
    error: {
      appenders: ['consoleOut', 'error'],
      level: 'warn',
    },
  },
 // 普通级别的logger,输出到控制台和日期分类的文件
const defaultLogger = log4js.getLogger('default')
 // 错误信息的logger,输出到控制台和error.log中
const errorLogger = log4js.getLogger('error')


Bug记录



父子进程同时写log4j日志时失败


当我用父子进程进行通信的时候,我需要在两个进程中都要记录一些log4j的日志信息。但是发现只有子进程的日志信息书写进了文件里面,而父进程的日志信息没有记录到。后来发现原来是不能两个进程不能同时写文件,所以我们只好单独开一个进程,把所有的信息交给这个进程来处理。


消息群发???


我们开不同的客户端,理论上应该是只能接收到自己的消息。但是在编码的过程中却发现能获得所有的消息。我们创建了一个父进程,父进程对应多个子进程,但是客户端获得了所有子进程的消息。子进程创建的实例太多,连自己都不清楚谁发送了消息。

改进:将子进程的实例和websocket连接成功后的ws实例储存起来,并生成uuid给定一个唯一的值,这样在收发消息的时候,根据uuid发送给指定的子进程,这样在创建子进程的过程当中,就不会失去对子进程的控制。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
Windows
按键精灵脚本-windows桌面自动化操作
按键精灵脚本-windows桌面自动化操作
590 0
|
监控 C# Windows
内网桌面监控软件中的远程控制功能实现(基于C#和Windows Forms)
近年来,随着远程办公的兴起,对内网桌面监控软件的需求逐渐增加。本文将探讨如何通过C#和Windows Forms实现内网桌面监控软件中的远程控制功能,并在结尾部分介绍监控到的数据如何自动提交到网站。
397 0
在网页端打开本地桌面客户端程序
在网页端打开本地桌面客户端程序
215 0
|
Windows
【电脑控制手机屏幕】windows11、10自带投屏功能,三步解决
想用电脑控制手机,但是下载第三方软件好麻烦,只需三步骤即可使用windows系统自带投屏插件实现投屏功能
1425 0
|
Web App开发 JavaScript 前端开发
Win + R 便捷打开常用网站
根据上一篇 Win + R 打开常用软件及文件夹 可以让电脑桌面非常简洁、干净,便捷指令快速开启应用程序、常用文件夹。这篇就介绍 `Win + R` 便捷打开常用网站。
|
Linux
全网独家:LINUX登录桌面后,如何自动运行自己的应用程序
全网独家:LINUX登录桌面后,如何自动运行自己的应用程序
220 0
|
Windows
使用360安全卫士实现应用程序不联网及删除右键菜单等
使用360安全卫士实现应用程序不联网及删除右键菜单等
258 0
使用360安全卫士实现应用程序不联网及删除右键菜单等
|
监控 网络协议 前端开发
如何从Web浏览器远程监视Linux服务器和桌面
当你要监视一台Linux机器时,你会有很多的选择。虽然现在有很多产品质量监控方案(比如 Nagios、 Zabbix、 Zenoss),它们拥有华丽的UI,可扩展监控,易于理解的报告等等,这些方案对于大多数终端用户都太过于强大了。如果你只需检查Linux服务器或桌面的基本状态(比如,CPU负载、内存使用、活跃进程),就请考虑一下linux-dash。
456 0
如何从Web浏览器远程监视Linux服务器和桌面
怎么删除 我的电脑-->管理-->服务和应用程序下的 服务
怎么删除 我的电脑-->管理-->服务和应用程序下的 服务
137 0