Node.js事件驱动和非阻塞I/O的特性,使其在高效处理I/O方面有较大优势。也正由于这些特性,Node在某些类型的业务处理上并不擅长。比如处理一个计算量较大的CPU密集型业务时,就可能会造成事件阻塞。这时可以使用child_process模块启动一个子进程来处理这一任务,从而释放事件循环。子进程查模块还可以用来执行一个外部命令(如:Linux命令、脚本等)。
在当前Node.js的版本(v0.12.4)中,child_process模块提供了4个异步方法可以用来创建子进程:exec
、execFile
、fork
、spawn
,这些方法中除fork
方法外都提供了同步版本的方法。本文将结合一些应用场景介绍child_process模块的用户。
1. 执行外部命令:child_process.exec(command[, options], callback)
1.1 使用示例
执行外部命令可以使用exec
方法,与spawn
方法相比,exec
方法更为便捷,exec
提供一个回调函数,可以更方便的处理命令执行结果的。示例哪下:
var exec = require('child_process').exec; exec('cat *.js bad_file | wc -l', function (error, stdout, stderr) { console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); if (error !== null) { console.log('exec error: ' + error); //没有错误产生,所以本行无输出 } });
在上面代码中,执行cat *.js bad_file | wc -l
命令,用于统计当前目录下所有“.js”文件和名为“bad_file”文件的内容行数,由于“bad_file”文件不存在,因此会产生一个"stderr"。执行结果如下:
stdout: 548 stderr: cat: bad_file: No such file or directory
1.2 参数说明
exec
命令有三个参数,command和callbakc为必传参数,options为可选参数:
-
command
要执行的shell命令。字符串类型 -
options
可选参数对象-
cwd
执行命令的工作目录,未设置时为当前目录。字符串类型 -
env
传递给子进程的环境变量,默认值null,为null时会继承父进程的环境变量 -
encoding
子进程输出的编码格式,默认值: 'utf8' -
shell
执行命令的shell类型,默认值:UNIX为 '/bin/sh' on UNIX, Windows为 'cmd.exe' -
timeout
执行命令的超时时间,单位为毫秒, 默认值:0 -
maxBuffer
stdout流和stderr流的最大容量,单为字节,超出后子进程会中止。默认值:200*1024
-
killSignal
超时或超出缓存容量发送到子进程的信号。默认值:'SIGTERM'(进程中止),此信号与UNIX信号一致。 -
uid
执行进程用户的uid -
gid
执行进程的gid
-
-
callback
子进程执行完毕的回调函数-
error
Error类型 -
stdout
标准备输出,Buffer类型 -
stderr
标准报错,Buffer类型
-
- 返回值: ChildProcess 对象
child_process
模块允许你对子进程进行更加精细的控制,如子进程的启动、终止、与其进行通信等。除exec
方法外,child_process
模块中的其它方法都可以实现父子进程间的通信。子进程启动后,就创建了一个双向的通信通道,进程之间可以利用这个通道相互收发一些字符串形式的数据。父进程还可以对子进程发送一些控,如发送信号、终止进程等。
2. 创建子进程:child_process.spawn(command[, args][, options])
可以使用exec
方法启动外部进程,并在进程结束时调用回调函数。exec
方法使用简单,但也会有一些缺点。
-
除了命令行参数和环境变量外,
exec
方法不允许与子进程通信。 - 子进程的输出是被缓存后输出的,所以无法进行流处理,也可能引起内存耗尽的问题。
2.1 使用示例
spawn
方法用于从执行的命令创建一个新进程,命令的参数被做为数组传入,忽略参数时将传入一个空数组。
var child_process = require('child_process'); var child = child_process.spawn('tail', ['-f', '/var/log/system.log']);
以上命令用于监控系统日志,为了能看到进程的输出数据,我们还需要监听一些进程的事件。
//打印子进程的输出数据 child.stdout.on('data', function (data) { console.log('stdout: ' + data); }); //监听子进程的错误流数据 child.stderr.on('data', function (data) { console.log('stderr: ' + data); }); //监听子进程的退出事件 child.on('close', function (code) { console.log('子进程退出,code:' + code); });
2.2 参数说明
-
command
要执行的shell命令。字符串类型 -
args
要执行命令的参数列表。数组类型 -
options
可选参数对象-
cwd
执行命令的工作目录,未设置时为当前目录。字符串类型 -
env
传递给子进程的环境变量,默认值null,为null时会继承父进程的环境变量 -
stdio
子进程stdio配置。数组或字符串类型 -
'pipe'
-['pipe', 'pipe', 'pipe']
, 默认值 -
'ignore'
-['ignore', 'ignore', 'ignore']
-
'inherit'
-[process.stdin, process.stdout, process.stderr]
or[0,1,2]
-
customFds
数组类型(使用示例) -
detached
子进程是否有群组最高权限, Boolean类型。(使用示例) -
uid
执行进程用户的uid -
gid
执行进程的gid
-
- 返回值: ChildProcess 对象
3. 从可执行程序启动子进程:child_process.execFile(file[, args][, options][, callback])
execFile
方法用于从一个可执行程序启动一个子进程。与exec
相比,execFile
不启动独立的shell,因此更加轻量级。 execFile
也可以用于执行命令。
3.1 使用示例
创建.sh
文件,文件内容cat *.js bad_file | wc -l
,执行chmod +x sh
命令,为文件添加可执行属性。使用execFile
文件创建子进程,示例如下:
var child_process = require('child_process'); child_process.execFile('./sh', function (error, stdout, stderr) { console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); if (error !== null) { console.log('exec error: ' + error); } });
3.2 参数说明
execFile
命令有四个参数,file和callbakc为必传参数,options、args为可选参数:
-
file
要执行程度的文件或命令名。字符串类型 -
args
要执行程度或命令的参数列表。数组类型 -
options
可选参数对象,与exec
的options
对象相同 -
callback
子进程执行完毕的回调函数。与exec
的callback
函数相同 - 返回值: ChildProcess 对象
3.3 用execFile
方法执行命令
execFile
方法也可以用于执行shell命令。与exec
方法不同的是,execFile
执行命令需要将要执行的命令和参数分别传入。示例如下:
var child_process = require('child_process'); child_process.execFile('ls', ['-l', '/tmp'], function (error, stdout, stderr) { console.log('执行结果: ' + stdout); });
4. 运行Node模块:child_process.fork(modulePath[, args][, options])
fork
方法用于运行Node模块,fork
启动模块后会在父进程与子进程直接建立一个IPC管道,用于父子进程之间的通信。
4.1 使用示例
child.js
是一个Node.js
的模块,用fork
方法运行这个模块。
var child_process = require('child_process'); var child = child_process.fork('./child.js'); child.on('message', function(m) { console.log('收到了父进程的消息:', m); }); //发送消息到子进程 child.send({ hello: 'world' });
child.js
代码如下:
process.on('message', function(m) { console.log('子进程收到了消息:', m); }); //向父进程发送消息 process.send({ foo: 'bar' });
5. 进程控制与中止:child.kill([signal])
信号是进程间通信的一种简单方式,父进程可以通过信号对子进程进行控制。一些信号可以由子进程处理,而另一些信号只能由操作系统处理。如果进程收到一个不能处理的信号,进行就会终止。可以通过kill
方法向子进程发送一个信号,实现对子进程的控制。kill
方法默认发送的是SIGTERM
(进程中止)信号。通过kill
方法发送不同的signal
可以实现对子进程的控制,而子进程可以捕获一些可处理的信号,进行响应及处理。
var child_process = require('child_process'); var child = child_process.spawn('tail', ['-f', '/var/log/system.log']); //打印子进程的输出数据 child.stdout.on('data', function (data) { console.log('stdout: ' + data); }); //5秒后,进程中止 setTimeout(function(){ child.kill(); }, 5000);