Nodejs进程间通信
普通模式
在 Node.js 里,通过 Child Process 模块 child_process.fork() 方法 fork 出来的子进程,提供了一个 chind.send 来进行进程间的消息通讯:
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
// 监听 message 事件来接收子进程发送的消息
n.on('message', (m) => {
console.log('PARENT got message:', m);
});
// 向子进程发送消息
n.send({ hello: 'world' });
// 监听 *message* 事件,接收来自父进程发送的消息
process.on('message', (m) => {
console.log('CHILD got message:', m);
});
// 通过 process.send 向父进程发送消息
process.send({ foo: ‘bar’ });
实际上,通过 child_process.fork() 的方式 spawn 出来的子进程,默认激活了一个 IPC (进程间通信,Inter-Process Communication) 通道来进行通讯的。
但是使用 fork 方法有一个很大的局限性,既只能用于产生 Node.js 子进程,如果调用的是其它语言(比如其它诸多世界上最好的各种语言)的时候,就跪了,同时在很多场景下,单一的 IPC 通道并不能满足需求(性能、流式处理等),这个时候,就只能祭出 child_proecess.spawn() 来处理了。
进阶处理
Node.js 一共提供了四种方式来产生子进程:
- child_process.exec
- child_process.execFile
- child_process.fork
- child_process.spawn
实际上,前三种方法都是对 child_process.spawn 的特殊封装用于简化调用,那么既然 child_process.fork 能有方式激活 IPC 通道来进行进程间的通讯,直接 child_process.spawn 产生的子进程激活通讯通道也就不是什么问题了。
调用 child_process.spawn 时,有三个参数需要处理:
- command: 用来调用的命令
- args:用来传递给 command 参数调用的命令的参数
- options:一组产生子进程时的配置参数
我们需要的东西,就在 options.stdio 里。
options.stdio 用于配置建立父进程与子进程之间的通讯管道,他的值可以是一个数组或者字符串,当为数组时,每个索引对应子进程中的一个文件标识符,默认情况下,Node.js 会为其 spawn 的子进程打开三个文件标识符,既子进程的 stdin 、stdout、stderr 流会被重定向到 * ChildProcess* 对象的 child.stdin, child.stdout 和 child.stderr 上,而当 options.stdio 的值为字符串时,对应下面的三种情况:
- ‘pipe’:等价于 [‘pipe’, ‘pipe’, ‘pipe’] (默认情况)
- ‘ignore’:等价于 [‘ignore’, ‘ignore’, ‘ignore’]
- ‘inherit’:等价于 [process.stdin, process.stdout, process.stderr] 或者 [0,1,2]
在实际使用中,我们可以根据需要调整 options.stdio 的配置,比如忽略 stdin 和 stdout,并把 child 的 stderr 重定向到 process.stderr:
// parent.js
'use strict';
const child_process = require('child_process');
let worker = child_process.spawn('node', ['child.js'], {
stdio: ['ignore', 'ignore', process.stderr]
});
// child.js
'use strict';
console.log('Hello world!');
console.error('Hello error!');
此时执行 parent.js,仅会输出 Hello error!,因为我们配置忽略了 stdout 的输出。通过这三个值的灵活配置,我们可以实现多种样式的数据输入、输出。
高阶模式
默认情况下, child_process 在 spawn 的时候,只打开了三个文件标识符,而实际上,这个数量是可以继续扩展的,同时用于通讯的除了上面提到的 pipe 和 IPC,还可以直接使用 Stream,所以,你是不是已经想到了一些美妙的事情了:)
下面我们来扩展一下上面的例子,添加一条额外的 Stream 用于从 child 向 parent 发送数据。
// parent.js
'use strict';
const child_process = require('child_process');
let worker = child_process.spawn('node', ['child.js'], {
stdio: ['ignore', 'ignore', process.stderr, 'pipe']
});
// 监听创建的 Stream
worker.stdio[3].on('data', (chunk) => {
console.log(chunk.toString());
});
// child.js
'use strict';
const fs = require('fs');
let ws = fs.createWriteStream(null, {
fd: 3
});
let doit = () => {
ws.write(`Hello world! ${Date.now()}`, () => {
setTimeout(doit, 1000);
});
};
doit();
上面的实例中,因为我们明确知道当前打开的第四个文件标识符(索引从0开始)是我们在父进程中通过 ‘pipe’ 参数指定的,因此我们可以使用 fs.createWriteStream 的高阶用法,通过指定 fd 来创建一个 WriteStream。
使用同样的方式,可以继续扩充添加 stream 用于数据交换,从而实现进程间高效的数据交互。
实际上,这种进程间通讯的方式在各种语言中都是通用的,比如在之前阿里云的消息队列没有提供 Node SDK的情况下,因为业务需要,跟当时的小伙伴一起,利用这种方式,spawn 了一个PHP的子进程来调用阿里云的消息队列并进行数据交互,比较低成本的解决了一个很急业务需求。
相关文章
- nodejs-安装/helloworld/npm
- NodeJS中 package.json 解析
- Nodejs【单机】多进程模式集群
- ubuntu18.04安装最新nodejs
- nodejs注册为windows服务
- nodejs 读取目前下所有文件
- Jmeter接口测试-基于nodejs的to do list项目说明
- NodeJs中process.cwd()与__dirname的区别
- nodejs简单使用ejs模板引擎,如何获取get、post请求传值
- 浅析nodejs的require函数分别加载自定义模块和npm开源库的不同加载原理、NodeJS模块加载机制require和module的理解
- linux服务器安装nodeJS步骤及踩坑记录(解决node -v报错cannot execute binary file: Exec format error的问题 - 在Linux中安装适用于arm64位的nodejs)以及node环境项目部署
- nodejs框架koa,egg以及es6一起学
- 处理用千牛导出淘宝数据,供Logstash到Elasticsearch使用。(NodeJS)
- CommonJS、AMD、CMD、NodeJs、RequireJS到底有什么联系?