const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
默认情况下,Node.js进程和子进程之间的stdin,stdout,stderr管道是已经存在的。通常情况下这个方法可以以一种非阻塞的方式来传递数据。(注意,有些程序在内部使用line-buffered I/O。因为这也不会影响到Node.js,这意味着传递给子进程的数据可能不会马上消费)。
在一些特殊情况下,例如自动化shell脚本,同步的方法可能更加有用。多数情况下,同步的方法会对性能产生重要的影响,因为他会阻塞事件循环
child_process.spawn(), child_process.fork(), child_process.exec(), and child_process.execFile()都是异步的API。每一个方法都会产生一个ChildProcess实例,而且这个对象实现了Node.js的EventEmitter这个API,于是父进程可以注册监听函数,在子进程的特定事件触发的时候被调用。 child_process.exec() 和 child_process.execFile()可以指定一个可选的callback函数,这个函数在子进程终止的时候被调用。
在windows平台上执行.bat和.cmd:
child_process.exec和child_process.execFile的不同之处可能随着平台不同而有差异。在Unit/Linux/OSX平台上execFile更加高效,因为他不会产生shell。在windows上,.bat/.cmd在没有终端的情况下是无法执行的,因此就无法使用execFile(child_process.spawn也无法使用)。在window上,.bat/.cmd可以使用spawn方法,同时指定一个shell选项;或者使用child_process.exec或者通过产生一个cmd.exe同时把.bat/.cmd文件传递给它作为参数(child_process.exec就是这么做的)。
const spawn = require('child_process').spawn;
const bat = spawn('cmd.exe', ['/c', 'my.bat']);//使用shell方法指定一个shell选项
bat.stdout.on('data', (data) => {
console.log(data);
});
bat.stderr.on('data', (data) => {
console.log(data);
});
bat.on('exit', (code) => {
console.log(`Child exited with code ${code}`);
});
或者也可以使用如下的方式:
const exec = require('child_process').exec;//产生exec,同时传入.bat文件
exec('my.bat', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
child_process.exec(command[, options][, callback])
const exec = require('child_process').exec;
const child = exec('cat *.js bad_file | wc -l',
(error, stdout, stderr) => {
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
if (error !== null) {
console.log(`exec error: ${error}`);
}
});
上面的代码产生一个shell,然后使用这个shell执行命令,同时对产生的结果进行缓存。其中回调函数中的error.code属性表示子进程的exit code,error.signal表示结束这个进程的信号,任何非0的代码表示出现了错误。默认的options参数值如下:
{
encoding: 'utf8',
timeout: 0,
maxBuffer: 200*1024,//stdout和stderr允许的最大的比特数据,超过她子进程就会被杀死
killSignal: 'SIGTERM',
cwd: null,
env: null
}
如果timeout非0,那么父进程就会发送信号,这个信号通过killSignal指定,默认是"SIGTERM"来终结子进程,如果子进程超过了timeout指定的时间。注意:和POSIX系统上调用exec方法不一样的是,child_process.exec不会替换当前线程,而是使用一个shell去执行命令
const execFile = require('child_process').execFile;
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
因为不会产生shell,一些I/O redirection和file globbing这些行为不支持
最后,我们来看看子进程和父进程之间是如何通信的:
服务器端的代码:
var http = require('http');
var cp = require('child_process');
var server = http.createServer(function(req, res) {
var child = cp.fork(__dirname + '/cal.js');
//每个请求都单独生成一个新的子进程
child.on('message', function(m) {
res.end(m.result + '\n');
});
//为其指定message事件
var input = parseInt(req.url.substring(1));
//和postMessage很类似,不过这里是通过send方法而不是postMessage方法来完成的
child.send({input : input});
});
server.listen(8000);
子进程的代码:
function fib(n) {
if (n < 2) {
return 1;
} else {
return fib(n - 2) + fib(n - 1);
}
}
//接受到send传递过来的参数
process.on('message', function(m) {
//console.log(m);
//打印{ input: 9 }
process.send({result: fib(m.input)});
});
child_process.spawn(command[, args][, options])
const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
下面是一个很详细的运行"ps ax|grep ssh"的例子:
const spawn = require('child_process').spawn;
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);
ps.stdout.on('data', (data) => {
grep.stdin.write(data);
});
ps.stderr.on('data', (data) => {
console.log(`ps stderr: ${data}`);
});
ps.on('close', (code) => {
if (code !== 0) {
console.log(`ps process exited with code ${code}`);
}
grep.stdin.end();
});
grep.stdout.on('data', (data) => {
console.log(`${data}`);
});
grep.stderr.on('data', (data) => {
console.log(`grep stderr: ${data}`);
});
grep.on('close', (code) => {
if (code !== 0) {
console.log(`grep process exited with code ${code}`);
}
});
用下面的例子来检查错误的执行程序:
const spawn = require('child_process').spawn;
const child = spawn('bad_command');
child.on('error', (err) => {
console.log('Failed to start child process.');
});
options.detached:
const fs = require('fs');
const spawn = require('child_process').spawn;
const out = fs.openSync('./out.log', 'a');
const err = fs.openSync('./out.log', 'a');
const child = spawn('prg', [], {
detached: true,//依赖于父进程
stdio: [ 'ignore', out, err ]
});
child.unref();//允许父进程单独退出,不用等待子进程
当使用了detached选项去产生一个长期执行的进程,这时候如果父进程退出了那么子进程就不会继续执行了,除非指定了一个stdio配置(不和父进程之间有联系)。如果父进程的stdio是继承的,那么子进程依然会和控制终端之间保持关系。
const spawn = require('child_process').spawn;
// Child will use parent's stdios
//使用父进程的stdios
spawn('prg', [], { stdio: 'inherit' });
//产生一个共享process.stderr的子进程
// Spawn child sharing only stderr
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });
// Open an extra fd=4, to interact with programs presenting a
// startd-style interface.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });
注意:当子进程和父进程之间建立了IPC通道,同时子进程为Node.js进程,这时候开启的具有IPC通道的子进程(使用unref)直到子进程注册了一个disconnect的事件处理句柄process.on('disconnect'),这样就会允许子进程正常退出而不会由于IPC通道的打开而持续运行。
Class: ChildProcess
这个类的实例是一个EventEmitters,用于代表产生的子进程。这个类的实例不能直接创建,必须使用 child_process.spawn(), child_process.exec(), child_process.execFile(), or child_process.fork()来完成
'close'事件:
其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号;这个事件当子进程的stdio stream被关闭的时候触发,和exit事件的区别是多个进程可能共享同一个stdio streams!(所以一个进程退出了也就是exit被触发了,这时候close可能不会触发)
'exit'事件:
其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号。这个事件当子进程结束的时候触发,如果进程退出了那么code表示进程退出的exit code,否则没有退出就是null。如果进程是由于收到一个信号而终止的,那么signal就是这个信号,是一个string,默认为null。
注意:如果exit事件被触发了,子进程的stdio stream可能还是打开的;Node.js为SUGUBT,SIGTERM创建信号处理器,而且Node.js进程在收到信号的时候不会马上停止。Node.js会进行一系列的清理工作,然后才re-raise handled signal。见waitpid(2)
'disconnect'事件:
在子进程或者父进程中调用ChildProcess.disconnect()方法的时候会触发。这时候就不能再发送和接受信息了,这是ChildProcess.connected就是false了
'error'事件:
当进程无法产生的时候,进程无法杀死的时候,为子进程发送消息失败的时候就会触发。注意:当产生错误的时候exit事件可能会也可能不会触发。如果你同时监听了exit和error事件,那么就要注意是否会无意中多次调用事件处理函数
'message'事件:
message参数表示一个解析后的JSON对象或者初始值;sendHandle可以是一个net.Socket或者net.Server对象或者undefined。当子进程调用process.send时候触发
child.connected:
调用了disconnect方法后就会是false。表示是否可以在父进程和子进程之间发送和接受数据,当值为false就不能发送数据了
child.disconnect()
关闭子进程和父进程之间的IPC通道,这时候子进程可以正常退出如果没有其他的连接使得他保持活动。这时候父进程的child.connected和子进程的process.connected就会设置为false,这时候不能传输数据了。disconnect事件当进程没有消息接收到的时候被触发,当调用child.disconnect时候会立即触发。注意:当子进程为Node.js实例的时候如child_process.fork,这时候process.disconnect方法就会在子进程中调用然后关闭IPC通道。
child.kill([signal])
为子进程传入消息,如果没有指定参数那么就会发送SIGTERM信号,可以参见signal(7)来查看一系列信号
const spawn = require('child_process').spawn;
const grep = spawn('grep', ['ssh']);
grep.on('close', (code, signal) => {
console.log(
`child process terminated due to receipt of signal ${signal}`);
});
// Send SIGHUP to process
grep.kill('SIGHUP');
ChildProcess对象在无法传输信号的时候会触发error事件。为一个已经退出的子进程发送信号虽然无法报错但是可能导致无法预料的结果。特别的,如果这个PID已经被分配给另外一个进程那么这时候也会导致无法预料的结果。
const spawn = require('child_process').spawn;
const grep = spawn('grep', ['ssh']);
console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();//通过grep.stdin.end结束
child.send(message[, sendHandle][, callback])
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
n.on('message', (m) => {
console.log('PARENT got message:', m);
});
n.send({ hello: 'world' });
子进程为:
process.on('message', (m) => {
console.log('CHILD got message:', m);
});
process.send({ foo: 'bar' });
子进程使用process.send方法为父进程发送消息。有一个特例,发送{cmd: 'NODE_foo'}。当一个消息在他的cmd属性中包含一个NODE_前缀会被看做使用Node.js核心(被Node.js保留)。这时候不会触发子进程的process.on('message')。而是使用process.on('internalMessage')事件,同时会被Node.js内部消费,一般不要使用这个方法。sendHandle这个用于给子进程传入一个TCP Server或者一个socket,为process.on('message')回调的第二个参数接受。callback当消息已经发送,但是子进程还没有接收到的时候触发,这个函数只有一个参数成功为null否则为Error对象。如果没有指定callback同时消息也不能发送ChildProcess就会触发error事件。当子进程已经退出就会出现这个情况。child.send返回false如果父子进程通道已经关闭,或者积压的没有传输的数据超过一定的限度,否则这个方法返回true。这个callback方法可以用于实现流控制:
const child = require('child_process').fork('child.js');
// Open up the server object and send the handle.
const server = require('net').createServer();
server.on('connection', (socket) => {
socket.end('handled by parent');
});
server.listen(1337, () => {
child.send('server', server);
});
子进程接受这个消息:
process.on('message', (m, server) => {
if (m === 'server') {
server.on('connection', (socket) => {
socket.end('handled by child');
});
}
});
这时候server就被子进程和父进程共享了,一些连接可以被父进程处理,一些被子进程处理。上面的例子如果使用dgram那么就应该监听message事件而不是connection,使用server.bind而不是server.listen,但是当前只在UNIX平台上可行。
const normal = require('child_process').fork('child.js', ['normal']);
const special = require('child_process').fork('child.js', ['special']);
// Open up the server and send sockets to child
const server = require('net').createServer();
server.on('connection', (socket) => {
// If this is special priority
if (socket.remoteAddress === '74.125.127.100') {
special.send('socket', socket);
return;
}
// This is normal priority
normal.send('socket', socket);
});
server.listen(1337);
子进程为:
process.on('message', (m, socket) => {
if (m === 'socket') {
socket.end(`Request handled with ${process.argv[2]} priority`);
}
});
当socket被发送到子进程的时候那么父进程已经无法追踪这个socket什么时候被销毁的。这时候.connections属性就会成为null,因此我们建议不要使用.maxConnections。注意:这个方法在内部JSON.stringify去序列化消息
child.stderr:
一个流对象,是一个可读流表示子进程的stderr。他是child.stdio[2]的别名,两者表示同样的值。如果子进程是通过stdio[2]产生的,设置的不是pipe那么值就是undefined。
child.stdin:
一个可写的流对象。注意:如果子进程等待读取输入,那么子进程会一直等到流调用了end方法来关闭的时候才会继续读取。如果子进程通过stdio[0]产生,同时不是设置的pipe那么值就是undefined。child.stdin是child.stdio[0]的别名,表示同样的值。
const spawn = require('child_process').spawn;
const grep = spawn('grep', ['ssh']);
console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();//通过grep.stdin.end结束
child.stdio:
const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');
const child = child_process.spawn('ls', {
stdio: [
0, // Use parents stdin for child
'pipe', // Pipe child's stdout to parent
fs.openSync('err.out', 'w') // Direct child's stderr to a file
]
});
assert.equal(child.stdio[0], null);
assert.equal(child.stdio[0], child.stdin);
assert(child.stdout);
assert.equal(child.stdio[1], child.stdout);
assert.equal(child.stdio[2], null);
assert.equal(child.stdio[2], child.stderr);
child.stdout: