在游戏项目开发中,经常会遇到有大厅内含多个子程序的模式,这时候使用nodeJs做服务端时会涉及到对多个子进程的管理和框架问题,结合以往的实际开发经验,对这种模式的服务端设计做个简单的总结。
1. 核心模块
child_process和fs
child_process模块主要用于在主进程运行的时候衍生出独立于主进程的异步子进程。
fs模块主要用于在主进程内读取需要的配置文件
2. 实现方式
首先需要一个配置文件来配置子进程的信息,一般使用json文件,例如:
[
{
“name”:”game1”,
“path”:”Game/game1”,
“port”:1100,
“status”:0,
“des”:”子进程一”
},
{
“name”:”game2”,
“path”:”Game/game2”,
“port”:1200,
“status”:0,
“des”:”子进程二”
},
{
“name”:”game3”,
“path”:”Game/game3”,
“port”:1300,
“status”:0,
“des”:”子进程三”
}
]
这样一个json文件自定义了每个需要运行的子进程的名字,运行的js文件的路径,端口,服务端的状态,描述这些信息。
下面函数示例如可在运行的主进程的用这样一个方法去获取配置文件信息
/**
* 获取配置文件
* @param {string}manifestUrl 配置json路径
* @returns {Promise<Array<any>>}
*/
private async getManifest(manifestUrl:string):Promise<Array<any>> {
let manifest = [];
let fileName = await path.resolve(__dirname,manifestUrl);
let exists = await fs.existsSync(fileName);
if (exists) {
let manifestStr = awaitfs.readFileSync(fileName);
if (manifestStr) {
try {
manifest = await JSON.parse(manifestStr.toString("utf-8"));
} catch (e) {
console.error(config.manifestFileName+ "错误!", e.message);
}
}
} else {
console.error(config.manifestFileName+ "不存在!");
process.exit(0);
}
return manifest;
}
下面函数示例可运行一个单个的进程一个item就是获取到的配置对象的一个节点元素
/**
* 运行一个实例
* @param item
* @param restart
*/
private async runInstance(item: any, restart = 0) {
let instanceObj = {
name: item.name,
desc: item.desc,
path: item.path,
port: item.port,
status: item.status,
cpu: "0%",
memory: "0%",
restart: restart,
instance: null,
version: "0"
};
if (!instanceObj["timer"]){
instanceObj["timer"] = setTimeout(()=> {
if (instanceObj.status== 0) {
instanceObj.restart = 0;
instanceObj["timer"]= null;
delete instanceObj["timer"];
}
}, 500);
}
if (item.status != 1) {
let arg = {
port: item.port,
serverName: item.name,
};
// 日志路径
let logPath = "";
let pathArr = item.path.split("/");
for (let i = 0; i< pathArr.length - 1; i++) logPath += pathArr[i] + "/";
instanceObj["logPath"]= logPath + "Logs.txt";
// fork模式运行
instanceObj.instance = cps.fork(path.resolve(__dirname,"./controller/" + item.path), [encodeURIComponent(JSON.stringify(arg))],{silent: true});
// 监听输出事件
instanceObj.instance.stdout.on("data",async (data: Buffer) => {
// todo 写入流到 log.txt
});
// 错误事件
instanceObj.instance.stderr.on("data",async (data: Buffer) => {
// todo 重启 写入错误日志等等
});
// 接收消息
instanceObj.instance.on("message",async (msg: any) => {
if (msg &&msg.method && this[msg.method]) {
instanceObj &&instanceObj.instance && instanceObj.instance.send({
method: msg.method,
data: await this[msg.method](...msg.arg)
})
}
});
}
}
这种模式管理每个进程的内存,cpu 版本信息都很方便,所有子进程可以通过继承方式继承一个类,通过process.argv[2]进行端口,版本等信息的管理。