Node.js中execFile,spawn,exec和fork简介
Node.js子流程child_process模块提供四种不同方法执行外部应用:
所有这些都是异步,调用这些方法会返回一个对象,这对象是ChildProcess类的实例。
1. execFile
用于执行一个外部应用,应用退出后会返回一些可选蚕食和带有缓冲输出的callback。
child_process.execFile(file[, args][, options][, callback])
文件 将要执行外部应用的可执行文件的路径或名称
参数 是字符串数组
可选
cwd 子流程当前工作目录
env key-value环境值对
encoding (默认: ‘utf8’)
timeout (Default: 0)
maxBuffer 标准输入或输出的大量数据 (in bytes) – 如果超过子流程会被杀死 (Default:200\*1024)
killSignal (Default: ‘SIGTERM’)
uid 流程的用户标识符 (See setuid(2).)
gid 流程的群组标识符(See setgid(2).)
当外部应用存在时,Node程序将携带参数"-version"被执行,当外部应用退出时,回调函数被调用,回调函数带有子流程的标准输入输出,来自外部应用的标准输出将被内部缓冲保存。
运行下面代码将打印当前node版本:
const execFile = require( 'child_process').execFile;
const child = execFile( 'node', [ '--version'], ( error, stdout, stderr) => {
if ( error) {
console.error( 'stderr', stderr);
throw error;
}
console.log( 'stdout', stdout);
});
node是如何发现外部应用?它是由PATH环境变量,其中会指定一系列目录,可执行的外部应用驻留在这些目录中,如果外部应该被发现存在,无需该外部应用的绝对路径或相对路径就会被定位。
execFile是当需要执行外部应用并获得输出时使用,我们能使用它运行一个图片处理应用将图片从PNG转为JPG格式,我们只关心其成功与否,当外部应用产生大量数据以及我们需要实时使用这些数据时,execFile就不要使用。
2.spawn
spawn 方法会在新的流程执行外部应用,返回I/O的一个流接口。
child_process.spawn(command[, args][, options])
使用案例:
const spawn = require( 'child_process').spawn;
const fs = require( 'fs');
function resize( req, resp) {
const args = [
"-", // use stdin
"-resize", "640x", // resize width to 640
"-resize", "x360
"-gravity", "center", // sets the offset to the center
"-crop", "640x360+0+0", // crop
"-" // output to stdout
];
const streamIn = fs.createReadStream( './path/to/an/image');
const proc = spawn( 'convert', args);
streamIn.pipe( proc.stdin);
proc.stdout.pipe( resp);
}
上面是一个express.js控制器函数中代码,我们使用流从一个图片文件读取,然后使用spawn方法生成convert程序,我们使用图片流喂给ChildProcess proc,只要这个proc对象产生数据,我们写入数据到一个可写的流中,用户无需等待整个图片转换完毕就能立即看见图片。
spawn返回一个对象流,对于输出大量数据然后需要读取的应用适合,因为是基于流,所有流好处有:
低内存损耗
自动处理后压back-pressure
在缓冲块中懒生产或消费数据
基于事件且非堵塞
缓冲可以让你超过V8 heap内存限制
3.exec
这个方法将会生成一个子shell,能够在shell中执行命令,并缓冲产生的数据,当子流程完成后回调函数将会被调用,可带有:
当命令成功执行,缓冲的数据
当命令失败,错误信息
child_process.exec(command[, options][, callback])
与execFile 和 spawn相比,exec并没有参数,因为exec允许我们在shell中执行多个命令,当使用exec时,我们如果需要传输参数到命令行,它们应该作为整个命令字符串的一部分。
下面代码将会输出当前目录下所有递归条目:
const exec = require( 'child_process').exec;
exec( 'for i in $( ls -LR ); do echo item: $i; done', ( e, stdout, stderr)=> {
if ( e instanceof Error) {
console.error( e);
throw e;
}
console.log( 'stdout ', stdout);
console.log( 'stderr ', stderr);
});
当在一个shell中运行命令,我们能实现在shell中所有功能,比如管道 重定向:
const exec = require( 'child_process').exec;
exec( 'netstat -aon | find "9000"', ( e, stdout, stderr)=> {
if ( e instanceof Error) {
console.error( e);
throw e;
}
console.log( 'stdout ', stdout);
console.log( 'stderr ', stderr);
});
上面案例中,node会生成一个子shell,并执行命令: netstat -aon | find “9000”
exec只有需要利用shell功能时才能使用。
4.fork
child_process.fork()方法是child_process.spawn()特殊情况,子流程返回一个ChildProcess对象,这个ChildProcess会有附加通讯通道,允许消息在父子之间来回穿梭。fork方法会打开一个IPC通道,允许Node流程之间传递消息:
如在子流程, process.on(‘message’) 和 process.send(‘message to parent’) 能被用来接受和发送数据
入在父流程, 使用child.on(‘message’) 和 child.send(‘message to child’)
每个流程都有自己的内存,都有它们自己的V8实例,启动至少30毫秒,每个大小是10mb
//parent.js
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' });
//sub.js
process.on( 'message', ( m) => {
console.log( 'CHILD got message:', m);
});
process.send({ foo: 'bar' });
因为Node主流程是单线程的,长期运行任务如计算等会堵塞主流程,因此,进来的请求不能被处理,应用变得不可响应,将这些长期运行任务放入主流程之外运行,fork一个新的node流程专门处理,这样主流程能够进行处理进来的请求保持可响应性。