前言

二、课程介绍

这个实验主要学习Node.js中的进程,涉及的模块是Cluster、Porcess和child_process。

Process模块

process模块在前面的实验已经用过了,使用时直接通过全局变量process访问,而不需要通过require方法加载。process用来和当前Node.js程序进程互动。
process是EventEmitter的一个实例,process主要包含<b>退出事件、信号事件以及一些属性</b>。

  • ###### 退出事件(exit)

当退出当前进程时,会促发exit事件,exit事件的回调函数中只接受同步操作,并且回调函数只接受一个参数,即退出代码,如:
<pre>process.on(‘exit’, function(code){
(function() {
console.log(‘This will not run’);
}, 0);
console.log(‘exit code: ‘, code);
});
</pre>
运行上面的代码,其中setTimeout方法中的回调函数是不会被执行的,因为exit事件中只会运行同步操作,而不会运行异步操作。
在exit事件之前还有一个beforeExit事件会被触发,在beforeExit的回调函数中可以执行异步操作。值得注意的是,通过process.exit()退出程序或者因为发生错误而退出程序是不会触发beforeExit事件的。顺便说一下,当有错误未被捕获时,就会<b>触发uncaughtException事件</b>。

  • ###### 信号事件

信号事件就是接收到某个特定信号才会被触发的事件。
比如SIGINT事件的触发方式是ctrl+c:
<pre>
// sigint.js
process.stdin.resume();
process.on(‘SIGINT’, function() {
console.log(‘Got SIGINT. Press Control-D to exit.’);
});
</pre>
运行代码:<pre>$ node sigint.js</pre>
然后按住control键,再按C键就会触发SIGINT事件。

本文主要对 Node.js 中进程管理相关的东西做一个简单介绍,包括 process
对象、child_process
模块和cluster
模块,详细的 API
可以查看官方文档,下面来看看详细的介绍吧。

属性

 process模块提供了很多属性,其中关于IO输入输出的主要有三个:
 process.stdin  // 标准输入
 process.stdout // 标准输出
 process.stderr // 标准错误

举例:
<pre>
// stdin.js
process.stdin.setEncoding(‘utf8’);
process.stdin.on(‘readable’, function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write(‘data: ‘ + chunk);
}
});
process.stdin.on(‘end’, function() {
process.stdout.write(‘end’);
});
</pre>
运行:<pre>node stdin.js</pre>

输入任意字符,Node.js会把输入的字符打印出来,输入ctrl+D触发end事件。

还有其他属性,比如process.argv是一个包含了命令行参数的数组。
方法

process模块还有很多实用的方法,比如:
process.cwd() // 返回脚本运行工作目录
process.chdir() // 切换工作目录
process.exit() // 退出当前进程
process.on() // 添加监听事件
//…


Process 对象

child_process模块

process 是 Node.js 的一个全局对象,可以在任何地方直接使用而不需要
require 命令加载。process 对象提供了 当前 node 进程
的命令行参数、标准输入输出、运行环境和运行状态等信息。

child_process用于创建子进程。

常用属性

child_process.spawn()方法

通过当前命令启动一个新的进程。如:
<pre>
// test_spawn.js
var spawn = require(‘child_process’).spawn,
ls = spawn(‘ls’, [‘-lh’, ‘/usr’]);
ls.stdout.on(‘data’, function (data) {
console.log(‘stdout: ‘ + data);
});
ls.stderr.on(‘data’, function (data) {
console.log(‘stderr: ‘ + data);
});
ls.on(‘close’, function (code) {
console.log(‘child process exited with code ‘ + code);
});
</pre>
运行命令:
<pre>$ node test_spawn.js</pre>
从结果可以看出,子进程成功运行了ls -lh /usr命令。
child_process.exec()方法

在shell中运行一个命令,并缓存其输出。如:
<pre>
// test_exec.js
var exec = require(‘child_process’).exec,
child;
child = 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);
}
});
</pre>
运行:
<pre>$ node test_exec.js</pre>
因为没有bad_file 这个文件,所以会看到终端打印出了相关错误信息。

argv

child_process.execFile()方法

与exec方法类似,执行特定程序文件,参数通过一个数组传送。,如:
<pre>
// test_execfile.js
var child_process = require(‘child_process’);
// exec: spawns a shell
child_process.exec(‘ls -lh /usr’, function(error, stdout, stderr){
console.log(stdout);
console.log(‘******************’);
});
// execFile: executes a file with the specified arguments
child_process.execFile(‘/bin/ls’, [‘-lh’, ‘/usr’], function(error,
stdout, stderr){
console.log(stdout);
});
</pre>
运行:
<pre>$ node test_execfile.js</pre>
#######child_process.fork()方法

直接创建一个子进程,此进程是node命令的子进程,fork(‘./sub.js’)相当于spwan(‘node’,
‘./sub.js’)。fork还会在父进程与子进程之间,建立一个通信管道,通过child.send()发送消息。如:
<pre>
// main.js
var cp = require(‘child_process’);
var n = cp.fork(__dirname + ‘/sub.js’);
n.on(‘message’, function(m) {
console.log(‘PARENT got message:’, m);
});
n.send({ hello: ‘world’ });
// sub.js
process.on(‘message’, function(m) {
console.log(‘CHILD got message:’, m);
});
process.send({ foo: ‘bar’ });
</pre>
运行:
<pre>$ node main.js</pre>
运行main.js会看到主进程收到了来自子进程的消息,而子进程也收到了来自主进程的消息。

process.argv 属性返回一个数组,第一个元素是
node,第二个元素是脚本文件名称,其余成员是脚本文件的参数。

cluster模块

单个的Node实例运行在单个线程中。要发挥多核系统的能力,需要启动一个Node进程集群来处理负载。cluster模块就用于创建共享服务器端口的子进程。
<pre>
// test_cluster.js
var cluster = require(‘cluster’);
var http = require(‘http’);
var numCPUs = require(‘os’).cpus().length; // 获取CPU内核数
// master是主进程
// 此处判断是否为master进程
// 是则根据CPU内核数创建worker进程
if (cluster.isMaster) {
// worker是运行节点
// 根据CPU数量启动worker
// Fork workers
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
Object.keys(cluster.workers).forEach(function(id) {
console.log(‘I am running with ID : ‘ +
cluster.workers[id].process.pid);
});
cluster.on(‘exit’, function(worker, code, signal) {
console.log(‘worker ‘ + worker.process.pid + ‘ died’);
});
} else {
// cluster.isWorker == true
// 运行到else中的代码
// 说明当前进程是worker进程
// 那么此worker进程就启动一个http服务
http.createServer(function(req, res) {
res.writeHead(200);
res.end(“hello world\n”);
}).listen(8000);
}
</pre>
运行程序:
<pre>$ node test_cluster.js</pre>

在终端会看到根据CPU内核数创建的子进程信息。

每个worker进程都是通过child_process.fork()方法产生的,所以它们可以通过IPC(进程间通信)与master进程通信。

$ node process-2.js one two=three four

0: /usr/local/bin/node
1: /Users/mjr/work/node/process-2.js
2: one
3: two=three
4: four
cluster.worker是worker进程对象,其中有 worker.id、worker.process等属性,还有worker.send()等方法。

env

process.env 返回一个对象,包含了当前 Shell 的所有环境变量,比如:

{
 TERM: 'xterm-256color',
 SHELL: '/bin/zsh',
 USER: 'huangtengfei',
 PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
 PWD: '/Users/huangtengfei',
 HOME: '/Users/huangtengfei'
}

这个属性通常的使用场景是,新建一个 NODE_ENV
变量,用来确定当前所处的开发阶段,生成阶段设为 production,开发阶段设为
develop ,然后在脚本中读取 process.env.NODE_ENV再做相应处理即可。

运行脚本时可以这样改变环境变量:

$ export NODE_ENV=production && node app.js
# 或者
$ NODE_ENV=production node app.js

stdin/stdout

process.stdin 指向标准输入(键盘到缓冲区里的东西),返回一个可读的流:

process.stdin.setEncoding('utf8');

process.stdin.on('readable', () => {
 var chunk = process.stdin.read();
 if (chunk !== null) {
 process.stdout.write(`data: ${chunk}`);
 }
});

process.stdin.on('end', () => {
 process.stdout.write('end');
});

process.stdout 指向标准输出(向用户显示内容),返回一个可写的流:

const fs = require('fs');

fs.createReadStream('wow.txt')
 .pipe(process.stdout);

常用方法

cwd()

process.cwd()返回运行 Node 的工作目录(绝对路径),比如在目录
/Users/huangtengfei/abc 下执行 node server.js,那么
process.cwd()返回的就是 /Users/huangtengfei/abc。

另一个常用的获取路径的方法是
__dirname,它返回的是执行文件时该文件在文件系统中所在的目录。注意process.cwd()
__dirname
的不同,前者是进程发起时的位置,后者是脚本的位置,两者可能不一致。

on()

process 对象部署了 EventEmitter 接口,可以使用
process.on()方法监听各种事件,并指定回调函数。比如监听到系统发出进程终止信号时关闭服务器然后退出进程:

process.on('SIGTERM', function () {
 server.close(function () {
 process.exit(0);
 });
});

exit()

process.exit() 会让 Node
立即终止当前进程(同步),参数为一个退出状态码,0 表示成功,大于 0
的任意整数表示失败。

kill()

process.kill()用来对特定 id 的进程(process.pid)发送信号,默认为
SIGINT 信号。比如杀死当前进程:

process.kill(process.pid, 'SIGTERM');

虽然名字叫 kill ,但其实
process.kill()只是负责发送信号,具体发送完信号之后这个怎么处理这个指定进程,取决于信号种类和接收到这个信号之后做了什么操作(比如process.exit()或者只是console.log('Ignored this single'))。

Child Process 模块

child_process
模块用于创建和控制子进程,其中最核心的是.spawn(),其他 API
算是针对特定场景对它的封装。使用前要先 require 进来:

const cp = require('child_process');

exec(command[, options][,
callback])

exec() 方法用于执行 shell
命令,它的第一个参数是字符串形式的命令,第二个参数(可选)用来指定子进程运行时的定制化操作,第三个参数(可选)用来设置执行完命令的回调函数。比如在一个特定目录
/Users/huangtengfei/abc 下执行ls -l 命令:

cp.exec('ls -l', {
 cwd: '/Users/huangtengfei/abc'
}, (error, stdout, stderr) => {
 if (error) {
 console.error(`exec error: ${error}`);
 return;
 }
 console.log(`stdout: ${stdout}`);
 console.log(`stderr: ${stderr}`);
})

spawn(command[, args][, options])

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图