Node之创建多进程应用程序
引
在Node.js中,只使用一个线程来执行所有的操作。因此,如果在应用程序中存在某个操作需要大量消耗CPU资源的情况,则其他操作都会受到一定的影响。例如,当服务器正在执行一个非常消耗CPU资源的操作,则在该操作执行之后接收的客户端请求都需要等待该操作执行完毕后才能被处理。、
近些年来,服务器一般都开始使用多核CPU或者多CPU,许多服务器应用程序都开始依靠多线程或多进程机制来处理这些请求,以便可以更好地利用这些CPU资源。在Node.js中,同样提供一个child_process模块。通过该模块的使用,在Node.js应用程序的主进程运行之后,可以开启多个子进程。在多个子进程之间可以共享内存空间,可以通过子进程之间的互相通信来实现信息的交换,多个子进程之间也可以通过共享端口的方式将请求分配给多个子进程来执行。
使用spawn方法开启子进程
在child_process模块中,提供多个可以开启子进程的方法
首先,可以使用spawn方法开启一个用于运行某个命令的子进程。
child_process.spawn(command,[args],[options])
- command:-为一个字符串,用于指定需要运行的命令
- arg:参数值为一个数组,其中存放了所有运行该命令时需要使用的参数,参数的指定顺序与数组中的元素顺序保持一致,如果不使用args参数,默认参数值为一个空数组。
options:参数值为一个对象,用于指定开启子进程时使用的选项。在该对象中,可以指定的属性及属性值如下
- cwd:属性值为一个字符串,用于指定子进程的当前工作目录,可以使用相对路径或绝对路径来指定该目录。
- stdio:属性值为一个字符串或一个存放了三个元素的数组,用于设置子进程的标准输入/输出。
- customFds:属性值为一个数组,用于指定为子进程的标准输入/输出指定文件描述符,目前该属性值已不推荐使用。
- env:属性值为一个对象,用于以“键名/键值”的形式为子进程指定环境变量。不指定该属性值时子进程中没有可以使用的环境变量,而不是使用process.env属性值中指定的环境变量。
- detached:属性值为一个布尔值。如果属性值为true,该子进程为一个进程组中的领头进程。如果该进程为领头进程,当其父进程已不存在时,子进程也可以独立存在。该属性的默认属性值为false。
- uid:属性值为一个数值。用于设置子进程的用户ID,只在POSIX(即非Windows)操作系统中有效。
- gid:属性值为一个数值。用于设置子进程的组ID,只在POSIX(即非Windows)操作系统中有效。
- 'pipe':用于在父进程与子进程之间创建一个管道。在父进程中可以通过代表子进程的ChildProcess对象的stdio[0]属性值访问子进程的标准输入,可以通过ChildProcess对象的stdio[1]属性值访问子进程的标准输出,可以通过ChildProcess象的stdio[2]属性值访问子进程的标准错误输出。
- 'ipc':用于在父进程与子进程之间创建一个专用于传递消息或文件描述符的IPC通道。一个子进程最多可以拥有一个IPC通道文件描述符。设置该选项使子进程的send方法可以被使用。如果子进程在这个文件描述符中写入JSON格式的消息,将会触发子进程对象的message事件。如果子进程运行Node.js应用程序,IPC通道的存在将使进程对象的send方法可以被使用,也可使进程对象的message事件被触发。
- 'ignore':用于指定不为子进程设置文件描述符。在Node.js中为子进程使用0文件描述符(用于标准输入)、1文件描述符(用于标准输出)以及2文件描述(用于标准错误输出),如果这些文件描述符被忽略,Node.js将会把子进程的文件描述符定义为/dev/null(重定向到空设备文件)。
- Stream对象:用于指定子进程与父进程共享一个终端设备、文件、端口或管道。数据流的底层文件描述符将在子进程中被复制。
- 正整数值:用于指定父进程中被打开的文件描述符。该文件描述符在子进程中被共享,其效果与在父进程及子进程中共享一个Stream对象一样。
- null或undefined:使用默认值。对于标准输入/输出来说,在子进程中创建与父进程相连接的管道。对于用于读取文件的文件描述符3来说,在子进程中被忽略。
spawn方法返回一个隐式创建的代表子进程的ChildProcess对象。
spawn使用示例:
var cp=require('child_process');
var sp1 =cp.spawn('node',['test1.js','one','two','three','four'],{cwd:'./test'});
var sp2 =cp.spawn('node',['test2.js'],{stdio:'pipe'});
sp1.stdout.on('data',function (data) {
console.log('子进程标准输出: '+data);
sp2.stdin.write(data);
});
sp1.on('exit',function(code,signal) {
console.log('子进程退出,退出代码为:'+code);
process.exit();
});
父进程向子进程发送信号
在父进程中,可以使用子进程对象的kill方法向子进程发送信号。
child.kill([signal])
可以使用一个可选参数,参数值为用于描述该信号的字符串。默认参数值为’SIGTERM’(用于强制关闭进程)。
使用子进程的kill方法关闭子进程
var cp=require('child_process');
var sp1 =cp.spawn('node',['test1.js','one','two','three','four'],{cwd:'./test'});
var sp2 =cp.spawn('node',['test2.js'],{stdio:'pipe'});
sp1.stdout.on('data',function (data) {
console.log('子进程标准输出: '+data);
sp2.stdin.write(data);
sp1.kill();
});
sp1.on('exit',function(code,signal) {
if(!code)
console.log('子进程退出,退出信号为:'+signal);
else
console.log('子进程退出,退出代码为:'+code);
process.exit();
});
sp1.on('error',function (err) {
console.log('子进程开启失败: '+err);
process.exit();
});
如果利用spawn方法的options参数值对象的stdio属性中的'ipc'字符串值在父进程与子进程之间创建一个IPC通道,那么,当该通道关闭时,将触发子进程对象的disconnect事件。
var cp=require('child_process');
var sp1 =cp.spawn('node',['test1.js','one','two','three','four'],{cwd:'./test',stdio:['ipc','pipe','ignore']});
var sp2 =cp.spawn('node',['test2.js'],{stdio:['pipe']});
sp1.stdout.on('data',function (data) {
console.log('子进程标准输出: '+data);
sp2.stdin.write(data);
});
sp1.on('exit',function(code,signal) {
console.log('子进程退出,退出代码为:'+code);
process.exit();
});
sp1.on('error',function (err) {
console.log('子进程开启失败: '+err);
process.exit();
});
sp1.on('disconnect', function() {
console.log('IPC通道被关闭。');
});
使用fork方法开启子进程
在child_process模块中,可以使用fork方法开启一个专用于运行Node.js中的某个模块文件的子进程。
child_process.fork(modulePath,[args],[options])
- modulePath:参数值为一个字符串,用于指定需要运行的Node.js模块文件路径及文件名。
- arg:参数值为一个数组,其中存放了所有运行该模块文件时需要使用的参数,参数的指定顺序与数组中的元素顺序保持一致,如果不使用args参数,默认参数值为一个空数组。
options:参数值为一个对象,用于指定开启子进程时使用的选项
- cwd:属性值为一个字符串,用于指定子进程的当前工作目录,可以使用相路径或绝对路径来指定该目录。
- env:属性值为一个对象,用于以“键名/键值”的形式为子进程指定环境变量。不指定该属性值时子进程中没有可以使用的环境变量,而不是使用process.env属性值中指定的环境变量。
- encoding:属性值为一个字符串,用于指定标准输出及标准错误输出数据的编码格式。默认属性值为“utf8”。
- silent:属性值为一个布尔值,当属性值为false时,子进程对象与父进程对象共享标准输入/输出,当属性值为true时,子进程对象与父进程对象不共享标准输入/输出。默认属性值为false。
fork方法返回一个隐式创建的代表子进程的ChildProcess对象。
在使用fork方法时,当子进程中所有输入/输出操作执行完毕后,子进程不会自动退出。必须使用process.exit()方法将其显式退出。
当使用fork方法开启子进程后,可以使用子进程对象的send方法在父进程中向子进程发送消息,在子进程中也可以使用进程对象的send方法向父进程发送消息,
child.send(message,[sendHandle]) //
在父进程中向子进程发送消息
process.send(message,[sendHandle]) //
在父进程中向主进程发送消息
- message:参数值为一个对象,用于指定需要发送的消息。
- sendHandle:参数可以指定为一个当接收到对方发送消息后执行的回调函数,也可以为一个服务器对象或socket端口对象,以达到在父进程与子进程之间共享该对象的目的。
子父进程共享HTTP服务器
父进程
var http = require('http');
var child_process = require('child_process');
var fs = require('fs');
var child = child_process.fork('child.js');
var server = net.createServer();
server.listen(1337, '127.0.0.1', function () {
child.send('server', server);
console.log('父进程中的服务器已创建。');
var httpServer = http.createServer();
httpServer.on('request', function (req, res) {
if(req.url!=="/favicon.ico"){
var sum=0;
for(var i=0;i<1000000;i++){
sum+=i;
}
res.write("客户端请求在父进程中被处理。");
res.end("sum="+sum);
}
});
httpServer.listen(server);
});
子进程
var http = require('http');
process.on('message', function (msg,server) {
if (msg === 'server') {
console.log('子进程中的服务器已创建。');
var httpServer = http.createServer();
httpServer.on('request', function (req, res) {
if(req.url!=="/favicon.ico"){
sum=0;
for(var i=0;i<1000000;i++){
sum+=i;
}
res.write("客户端请求在子进程中被处理。");
res.end("sum="+sum);
}
});
httpServer.listen(server);
}
});
连续向服务端发送10次请求
var http = require('http');
var options = {
hostname: 'localhost',
port: 1337,
path: '/',
method: 'GET'
};
for(var i=0;i<10;i++){
var req = http.request(options,function(res) {
res.on('data', function (chunk) {
console.log('响应内容: '+chunk);
});
});
req.end();
}
父进程与子进程共享socket端口对象
父进程
var child = require('child_process').fork('child.js');
var server = require('net').createServer();
server.on('connection', function(socket) {
if (socket.remoteAddress!== '192.168.1.100') {
child.send('socket', socket);
return;
}
socket.end('客户端请求被父进程处理。');
});
server.listen(42367,'192.168.1.100');
子进程
process.on('message', function(m,socket) {
if (m === 'socket') {
socket.end('客户端请求被子进程处理。');
}
});
使用exec方法开启子进程
在child_process模块中,可以使用exec方法开启一个用于运行某个命令的子进程并缓存子进程中的输出结果。
child_process.exec(command,[options],[callback])
- command:参数值为一个字符串,用于指定需要运行的命令。
options:参数值为一个对象,用于指定开启子进程时使用的选项。
- cwd:属性值为一个字符串,用于指定子进程的当前工作目录,可以使用相对路径或绝对路径来指定该目录。默认属性值为null。
- env:属性值为一个对象,用于以“键名/键值”的形式为子进程指定环境变量。不指定该属性值时,子进程中没有可以使用的环境变量,而不是使用process.env属性值中指定的环境变量。默认属性值为null。
- encoding:属性值为一个字符串,用于指定标准输出及标准错误输出数据的编码格式。默认属性值为“utf8”。
- timeout:属性值为一个整数值,用于指定子进程的超时时间,单位为毫秒。当子进程的运行时间超过该时间时,Node.js将使用killSignal属性值所指定的信号强制关闭该子进程。默认属性值为0,表示不限定子进程的运行时间。
- maxbuffer:属性值为一个整数值,用于指定用于缓存标准输出数据及标准错误输出数据的缓存区的最大长度,如果标准输出数据及标准错误输出数据的总长度超过该属性值,子进程将被强制关闭。默认属性值为200*1024。
- killSignal:用于指定关闭子进程的信号,默认属性值为'SIGTERM'。
exec方法中使用的callback参数用于指定子进程终止时调用的回调函数
function(error, stdout, stderr) {
//回调函数代码略
}
- error:参数值为子进程被异常终止时触发的异常对象。
- stderr:为缓存了子进程标准错误输出数据的缓存区对象。
- stdout:为缓存了子进程标准输出数据的缓存区对象
exec方法与spawn方法的最大区别是,spawn方法可以在父进程中实时接收子进程中输出的标准输出流数据或标准错误输出流数据,因此是一个异步方法。如果使用exec方法,那么父进程必须等待子进程中的标准输出流数据或标准错误输出流数据全部缓存完毕后才能接收这些数据,因此是一个同步方法。
使用execFile方法开启子进程
在child_process模块中,可以使用execFile方法开启一个专用于运行某个可执行文件的子进程。
child_process.execFile(file,[args],[options],[callback])
- file:参数值为一个字符串,用于指定需要运行的可执行文件路径及文件名。
- args:参数值为一个数组,其中存放了所有运行该文件时需要使用的参数,参数的指定顺序与数 组中的元素顺序保持一致,如果不使用args参数,默认参数值为一个空数组。
- options:参数值为一个对象,用于指定开启子进程时使用的选项。在该对象中,可以指定的属性及属性值与exec方法中所使用的options参数值对象中可以指定的属性及属性值完全相同。
- callback:用于指定子进程终止时调用的回调函数,该回调函数的指定方法与exec方法中使用的callback参数值回调函数的指定方法完全相同。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。