玩转 node 子进程

您所在的位置:网站首页 个人借款借条怎么写图片 玩转 node 子进程

玩转 node 子进程

2023-10-01 04:38| 来源: 网络整理| 查看: 265

前言

在 node 中,child_process 模块的重要性不言而喻,特别如果你在前端工作中有编写一些前端工具,不可避免需要使用 child_process 这个模块,并且可以使用它进行文件压缩、脚本运行等操作,所以深入掌握它变得刻不容缓,希望通过本篇文章,你能和我一样彻底掌握 child_process 模块。

博客 github地址为:fengshi123/blog ,汇总了作者的所有博客,也欢迎关注及 star ~

一、创建子进程的方式

child_process 模块提供了以下 4 个方法用于创建子进程,并且每一种方法都有对应的同步版本:

spawn: 启动一个子进程来执行命令; exec:  启动一个子进程来执行命令,与 spawn 不同的是,它有一个回调函数获知子进程的状况; execFile: 启动一个子进程来执行可执行文件; fork:  与 spawn 类似,不同点在于它创建 Node 的子进程只需指定要执行的 JavaScript 文件模块即可;

基本用法和区分点如下:

const cp = require('child_process'); // spawn cp.spawn('node', ['./dir/test1.js'], { stdio: 'inherit' } ); // exec cp.exec('node ./dir/test1.js', (err, stdout, stderr) => { console.log(stdout); }); // execFile cp.execFile('node', ['./dir/test1.js'],(err, stdout, stderr) => { console.log(stdout); }); // fork cp.fork('./dir/test1.js', { silent: false } ); // ./dir/test1.js console.log('test1 输出...');

差异点:

spawn 与 exec、execFile 不同的是,后两者创建时可以指定 timeout 属性设置超时时间,一旦创建的进程运行超过设定的时间将会被杀死; exec 与 execFile 不同的是,exec 适合执行已有的命令,execFile 适合执行文件; exec、execFile、fork 都是 spawn 的延伸应用,底层都是通过 spawn 实现的;

差异列表如下:

类型回调/异常进程类型执行类型可设置超时spawn不支持任意命令不支持exec支持任意命令支持execFile支持任意可执行文件支持fork不支持NodeJavaScript 文件不支持 二、子进程列表 1、child_process.exec(command[, options][, callback])

创建一个 shell,然后在 shell 里执行命令。执行完成后,将 stdout、stderr 作为参数传入回调方法。 options 参数说明:

cwd:当前工作路径; env:环境变量; encoding:编码,默认是 utf8; shell:用来执行命令的 shell,unix 上默认是 /bin/sh,windows 上默认是 cmd.exe; timeout:默认是 0; killSignal:默认是 SIGTERM; uid:执行进程的 uid; gid:执行进程的 gid; maxBuffer: 标准输出、错误输出最大允许的数据量(单位为字节),如果超出的话,子进程就会被杀死;默认是 200*1024(即 200k )

备注:

如果 timeout 大于 0,那么,当子进程运行超过 timeout 毫秒,那么,就会给进程发送 killSignal 指定的信号(比如 SIGTERM)。 如果运行没有出错,那么 error 为 null。如果运行出错,那么,error.code 就是退出代码(exist code),error.signal 会被设置成终止进程的信号。(比如 CTRL+C 时发送的 SIGINT) 例子 1: 基本用法 执行成功,error 为 null;执行失败,error 为 Error 实例;error.code 为错误码; stdout、stderr 为标准输出、标准错误;默认是字符串,除非 options.encoding 为 buffer;注意:stdout、stderr 会默认在结尾加上换行符; const { exec } = require('child_process'); exec('ls', (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); }) exec('ls', {cwd: __dirname + '/dir'}, (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); }) 例子 2: 子进程输出/错误监听

除了例子1 中支持回调函数获取子进程的输出和错误外,还提供 stdout 和 stderr 对输出和错误进行监听,示例如下所示

const child = exec('node ./dir/test1.js') child.stdout.on('data', data => { console.log('stdout 输出:', data); }) child.stderr.on('data', err => { console.log('error 输出:', err); }) 2、child_process.execFile(file[, args][, options][, callback])

跟 .exec() 类似,不同点在于,没有创建一个新的 shell,options 参数与 exec 一样;

例子 1:执行 node 文件 const { execFile } = require('child_process'); // 1、执行命令 execFile('node', ['./dir/test1.js'], (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); }) 例子 2:执行 shell 脚本文件

需要注意的是,我们执行 shell 脚本的时候,并没有重新开一个 shell,即:我们在根目录下运行 execFile 命令执行 ./dir/test2.sh 脚本,我们在 ./dir/test2.sh 脚本中执行与 test2.sh 同目录的 test1..js 文件,我们不能直接写成 node .test1.js 会找不到文件,应该从根目录去寻找; 注意:shell 脚本文件中如果需要访问 node 环境中的变量,可以将变量赋值给 process.env,这样在 shell 脚本中就可以通过 $变量名 进行直接访问;

const { execFile } = require('child_process'); // 2、执行 shell 脚本 // 在 shell 脚本中可以访问到 process.env 的属性 process.env.DIRNAME = __dirname; execFile(`${__dirname}/dir/test2.sh`, (error, stdout, stderr) => { if (error) { console.error('error:', error); return; } console.log('stdout: ' + stdout); // stdout: 执行 test2.sh test1 输出... console.log('stderr: ' + stderr); }) // ./dir/test2.sh #! /bin/bash echo '执行 test2.sh' node $DIRNAME/dir/test1.js // ./dir/test1.js console.log('test1 输出...'); 3、child_process.fork(modulePath[, args][, options])

(1)modulePath:子进程运行的模块; (2)args:字符串参数列表; (3)options 参数如下所示,其中与 exec 重复的参数就不重复介绍:

execPath: 用来创建子进程的可执行文件,默认是 /usr/local/bin/node。也就是说,你可通过 execPath 来指定具体的 node 可执行文件路径;(比如多个 node 版本) execArgv: 传给可执行文件的字符串参数列表。默认是 process.execArgv,跟父进程保持一致; silent: 默认是 false,即子进程的 stdio 从父进程继承。如果是 true,则直接 pipe 向子进程的child.stdin、child.stdout 等; stdio: 选项用于配置在父进程和子进程之间建立的管道,如果声明了 stdio,则会覆盖 silent 选项的设置; 例子 1:silent const { fork } = require('child_process'); // 1、默认 silent 为 false,子进程会输出 output from the child3 fork('./dir/child3.js', { silent: false }); // 2、设置 silent 为 true,则子进程不会输出 fork('./dir/child3.js', { silent: true }); // 3、通过 stdout 属性,可以获取到子进程输出的内容 const child3 = fork('./dir/child3.js', { silent: true }); child3.stdout.setEncoding('utf8'); child3.stdout.on('data', function (data) { console.log('stdout 中输出:'); console.log(data); }); 4、child_process.spawn(command[, args][, options])

(1)command:要执行的命令; (2)args:字符串参数列表; (2)options 参数说明,其它重复的参数不在重复:

argv0:显式地设置发送给子进程的 argv[0] 的值, 如果没有指定,则会被设置为 command 的值; detached:[Boolean] 让子进程独立于父进程之外运行; 例子 1:基础例子 const spawn = require('child_process').spawn; const ls = spawn('ls', ['-al']); // 输出相关的数据 ls.stdout.on('data', function(data){ console.log('data from child: ' + data); }); // 错误的输出 ls.stderr.on('data', function(data){ console.log('error from child: ' + data); }); // 子进程结束时输出 ls.on('close', function(code){ console.log('child exists with code: ' + code); });

结果截图如下: image.png

例子 2:声明 stdio

父子进程共用一个输出管道;

// 2、声明 stdio var ls = spawn('ls', ['-al'], { stdio: 'inherit' }); ls.on('close', function(code){ console.log('child exists with code: ' + code); });

结果截图如下: image.png

例子 3:错误场景 // 3、错误处理 // 3.1、场景1: 命令本身不存在,创建子进程报错 const child = spawn('bad_command'); child.on('error', (err) => { console.log('Failed to start child process 1: ', err); }); // 3.2、场景2: 命令存在,但运行过程报错 const child2 = spawn('ls', ['nonexistFile']); child2.stderr.on('data', function(data){ console.log('Error msg from process 2: ' + data); }); child2.on('error', (err) => { console.log('Failed to start child process 2: ', err); }); 三、常用事件

其继承自 EventEmitter 类, ChildProcess 的实例代表创建的子进程,其可以由以上介绍的几种创建子进程的方式创建得到。

3.1、常用的事件

(1)close 事件:子进程的 stdio 流关闭时触发; (2)disconnect 事件:事件在父进程手动调用 child.disconnect 函数时触发; (3)error 事件:产生错误时会触发; (4)exit 事件:子进程自行退出时触发; (5)message 事件:它在子进程使用 process.send() 函数来传递消息时触发;

3.2、示例 // 例子 const { fork } = require('child_process'); const child = fork('./dir/test5.js') child.on('close', (code, signal) => { console.log('close 事件:', code, signal); }) child.on('disconnect', () => { console.log('disconnect 事件...'); }) child.on('error', (code, signal) => { console.log('error 事件:', code, signal); }) child.on('exit', (code, signal) => { console.log('exit 事件:', code, signal); }) child.on('message', (val) => { console.log('message 事件:', val); }) // ./dir/test5.js console.log('event_test 输出...'); process.send('子进程发送给父进程的消息...')

各个事件捕获的顺序如下所示: image.png

四、进程间通信

通过以上 4 种 API 创建子进程后,父进程与子进程之间将会创建 IPC(Inter-Process Communication)通道,通过 IPC 通道,父子进程之间通过 message 和 send 来通信。Node 中实现 IPC 通道的是管道(pipe)技术,具体实现细节由 libuv 实现,在 Windows 下由命名管道(named pipe)实现,*nix 系统则采用 Unix Domain Socket 实现,IPC 创建和实现的示意图如下:

父进程在实际创建子进程之前,会创建 IPC 通道并监听它,然后才真正创建出子进程,并通过环境变量(NODE_CHANNEL_FD)告诉子进程这个 IPC 通道的文件描述符。子进程在启动过程中,根据文件描述符去连接这个已存在的 IPC 通道,从而完成父子进程之间的连接,创建 IPC 通道的示意图如下:

建立连接之后的父子进程就可以自由通信了,由于 IPC 通道是用命名管道或 Domain Socket 创建的,它们与网络 socket 的行为比较类似,属于双向通信。但是由于它们在系统内核中就完成了进程间的通信,而不用经过实际的网络层,非常高效。

例子 1:基本例子 // 父进程 const child3 = fork('./dir/child3_1.js'); child3.on('message', (m)=>{ console.log('message from child: ' + JSON.stringify(m)); }); child3.send({from: 'parent'}); // 子进程 process.on('message', function(m){ console.log('message from parent: ' + JSON.stringify(m)); }); process.send({from: 'child'}); 五、总结

本文对比了 node 创建子进程的 4 种方式 exec、execfile、fork、spawn 的异同点,并且介绍了它们常用的事件、IPC 通信,如有不足,欢迎指出。

博客 github地址为:fengshi123/blog ,汇总了作者的所有博客,也欢迎关注及 star ~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3