项目描述
该项目的需求是当我们使用noVnc远程连接另一台电脑后,能打开一款名叫tecplot的软件。
该项目时序图
项目实现思路
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的日志信息。但是发现只有子进程的日志信息书写进了文件里面,而父进程的日志信息没有记录到。后来发现原来是不能两个进程不能同时写文件,所以我们只好单独开一个进程,把所有的信息交给这个进程 来处理。
实例化一个Logger出来,再将此方法暴露出去,专门用来写日志信息
const log4js = require('log4js');
log4js.configure({
appenders: {
out: {
type: 'stdout',
},
dataFile: {
type: 'dateFile',
// pattern: '.yyyy-MM-dd-hh',
// compress: true,
numBackups:7,//7天之前的文件即删除
filename: '../logs/out.log',
keepFileExt: true,
maxLogSize: 20480000,
backups: 4,
},
},
categories: {
default:{
appenders:['out','dataFile'],
level:'debug',
},
},
pm2: true,
pm2InstanceVar: "INSTANCE_ID",
});
function recLog(msg, type) {
let logger = log4js.getLogger();
switch (type) {
case 'error':
logger.error(msg);
break;
case 'fatal':
logger.fatal(msg);
break;
default:
logger.info(msg);
break;
}
}
module.exports = {
recLog,
}
消息群发???
我们开不同的客户端,理论上应该是只能接收到自己的消息。但是在编码的过程中却发现能获得所有的消息。我们创建了一个父进程,父进程对应多个子进程,但是客户端获得了所有子进程的消息。子进程创建的实例太多,连自己都不清楚谁发送了消息。
改进:将子进程的实例和websocket连接成功后的ws实例储存起来,并生成uuid给定一个唯一的值,这样在收发消息的时候,根据uuid发送给指定的子进程,这样在创建子进程的过程当中,就不会失去对子进程的控制。