bob体育官方平台
bob体育官方平台Nodejs极简入门教程(三):进程,nodejs极简

复制代码 代码如下:var http = require;function fib { return 1; } else { return fib; }}var server = http.createServer { var num = parseInt, 10); res.writeHead; res.end;server.listen;

二、课程介绍

这个实验主要学习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事件。

Nodejs极简入门教程(三):进程,nodejs极简

Node 虽然自身存在多个线程,但是运行在 v8 上的 JavaScript 是单线程的。Node 的 child_process 模块用于创建子进程,我们可以通过子进程充分利用 CPU。范例:

复制代码 代码如下:

var fork = require('child_process').fork;
// 获取当前机器的 CPU 数量
var cpus = require('os').cpus();
for (var i = 0; i < cpus.length; i++) {
    // 生成新进程
    fork('./worker.js');
}

这里了解一下包括 fork 在内的几个进程创建方法:

1.spawn(command, [args], [options]),启动一个新进程来执行命令 command,args 为命令行参数
2.exec(command, [options], callback),启动一个新进程来执行命令 command,callback 用于在进程结束时获取标准输入、标准输出,以及错误信息
3.execFile(file, [args], [options], [callback]),启动一个新进程来执行可执行文件 file,callback 用于在进程结束时获取标准输入、标准输出,以及错误信息
4.fork(modulePath, [args], [options]),启动一个新进程来执行一个 JavaScript 文件模块,这时候创建的是 Node 子进程

Node 进程间通信

父进程

复制代码 代码如下:

// parent.js
var fork = require('child_process').fork;
// fork 返回子进程对象 n
var n = fork('./child.js');
// 处理事件 message
n.on('message', function(m) {
    // 收到子进程发送的消息
    console.log('got message: ' + m);
});
 
// 向子进程发送消息
n.send({hello: 'world'});

子进程

复制代码 代码如下:

// child.js
// 处理事件 message
process.on('message', function(m) {
    console.log('got message: ' + m);
});
 
// process 存在 send 方法,用于向父进程发送消息
process.send({foo: 'bar'});

需要注意的是,这里的 send 方法是同步的,因此不建议用于发送大量的数据(可以使用 pipe 来代替,详细见:
特殊的情况,消息中 cmd 属性值包含 NODE_ 前缀(例如:{cmd: ‘NODE_foo'} 消息),那么此消息不会被提交到 message 事件(而是 internalMessage 事件),它们被 Node 内部使用。

send 方法的原型为:

复制代码 代码如下:

send(message, [sendHandle])

这里,sendHandle(handle)可以被用于发送:

1.net.Native,原生的 C++ TCP socket 或者管道
2.net.Server,TCP 服务器
3.net.Socket,TCP socket
4.dgram.Native,原生的 C++ UDP socket
5.dgram.Socket,UDP socket

send 发送 sendHandle 时实际上不是(也不能)直接发送 JavaScript 对象,而是发送文件描述符(最终以 JSON 字符串发送),其他进程能够通过这个文件描述符还原出对应对象。

现在看一个例子:

父进程

复制代码 代码如下:

// parent.js
var fork = require('child_process').fork;
 
var n = fork('./child.js');
 
var server = require('net').createServer();
server.listen(7000, function() {
    // 发送 TCP server 到子进程
    n.send('server', server);
}).on('connection', function() {
    console.log('connection - parent');
});

子进程

复制代码 代码如下:

process.on('message', function(m, h) {
    if (m === 'server') {
        h.on('connection', function() {
            console.log('connection - child');
        });
    }
});

通过端口 7000 访问此程序,得到输出可能为 connection – parent 也可能得到输出 connection – child。这里子进程和父进程同时监听了端口 7000。通常来说,多个进程监听同一个端口会引起 EADDRINUSE 的异常,而此例的情况是,不同的两个进程使用了相同的文件描述符,且 Node 底层在监听端口时对 socket 设置了 SO_REUSEADDR 选项,这使得此 socket 可以在不同的进程间复用。在多个进程监听同一个端口时,同一时刻文件描述符只能被一个进程使用,这些进程对 socket 的使用是抢占式的。

cluster 模块

在 Node 的 v0.8 新增了 cluster 模块,通过 cluster 模块能够轻松的在一台物理机器上构建一组监听相同端口的进程。范例:

复制代码 代码如下:

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
 
// 检查进程是否是 master 进程
if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; ++i)
        // 生成新的 worker 进程(只有 master 进程才可以调用)
        cluster.fork();
 
    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    });
} else {
    http.createServer(function(req, res) {
        res.writeHead(200);
        res.end('hello worldn');
    }).listen(8000);
}

我们在 worker 进程中调用 listen 方法,监听请求将会传递给 master 进程。如果 master 进程已经存在一个正在监听的 server 符合 worker 进程的要求,那么此 server 的 handle 将会传递给 worker,如果不存在,master 进程则会创建一个,然后将 handle 传递给 worker 进程。

更多详细的关于 cluster 的文档:

以上示例提供了一个斐波纳契数列的计算服务,由于此计算相当耗时,且是单线程,当同时有多个请求时只能处理一个,通过child_process.fork()就可以解决此问题

属性

 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() // 添加监听事件
//...


JSON,JS,NODEJS三者的关系是怎?

三者性质完全不一样
JS是JavaScript语言,是一种解释性编程语言
JSON是JavaScript Object Notation,意思是JS语言中对象的表达法,常用于数据传输(与XML的作用类似),常在AJAX中替代XML
NodeJS是一种服务端平台,可以在服务端运行用JavaScript写的服务端脚本

注意的是:JS中函数本身就是个对象,所以函数可以作为形参不是NodeJS独有的,准确的说,NodeJS就是用Chrome浏览器的Google V8解释器来解释JS

总结来说:
JS是个编程语言
JSON是一种数据格式(没有逻辑只有数据)
NodeJS是个软件(JS服务端运行环境)

顺带一提:HTML是XML的派生(HTML是一种XML)但是为了适应其特定作用而有所改变,HTML(XML)与JSON同为数据表达语言,严格来讲并不包含逻辑只包含数据。  

这里引用一下官网上的一个示例,通过这个例子可以很好的理解fork()的功能

child_process模块

什是nodejs的事件驱动编程

nodejs是单进程单线程,但是基于V8的强大驱动力,以及事件驱动模型,nodejs的性能非常高,而且想达到多核或者多进程也不是很难(现在已经有大量的第三方module来实现这个功能)。
这里主要不是介绍nodejs具体应用代码,而是想介绍一下事件驱动编程。
Dan York介绍了两种典型的事件驱动实例。
第一个例子是关于医生看病。
在美国去看医生,需要填写大量表格,比如保险、个人信息之类,传统的基于线程的系统(thread-based system),接待员叫到你,你需要在前台填写完成这些表格,你站着填单,而接待员坐着看你填单。你让接待员没办法接待下一个客户,除非完成你的业务。
想让这个系统能运行的快一些,只有多加几个接待员,人力成本需要增加不少。
基于事件的系统(event-based system)中,当你到窗口发现需要填写一些额外的表格而不仅仅是挂个号,接待员把表格和笔给你,告诉你可以找个座位填写,填完了以后再回去找他。你回去坐着填表,而接待员开始接待下一个客户。你没有阻塞接待员的服务。
第二个例子是快餐店点餐。
在基于线程的方式中(thread-based way)你到了柜台前,把你的点餐单给收银员或者给收银员直接点餐,然后等在那直到你要的食物准备好给你。收银员不能接待下一个人,除非你拿到食物离开。想接待更多的客户,容易!加更多的收银员!
当然,我们知道快餐店其实不是这样工作的。他们其实就是基于事件驱动方式,这样收银员更高效。只要你把点餐单给收银员,某个人已经开始准备你的食物,而同时收银员在进行收款,当你付完钱,你就站在一边而收银员已经开始接待下一个客户。在一些餐馆,甚至会给你一个号码,如果你的食物准备好了,就呼叫你的号码让你去柜台取。关键的一点是,你没有阻塞下一个客户的订餐请求。你订餐的食物做好的事件会导致某个人做某个动作(某个服务员喊你的订单号码,你听到你的号码被喊到去取食物),在编程领域,我们称这个为回调(callback function)。
相反的,Node.Js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)。
考虑下面这个过程:
你用浏览器访问nodejs服务器上的"/about.html"
nodejs服务器接收到你的请求,调用一个函数从磁盘上读取这个文件。
这段时间,nodejs webserver在服务后续的web请求。
当文件读取完毕,有一个回调函数被插入到nodejs的服务队列中。
nodejs webserver运行这个函数,实际上就是渲染(render)了about.html页面返回给你的浏览器。
好像就节省了几微秒时间,但是这很重要!特别是对于需要相应大量用户的web server。  

Node 虽然自身存在多个线程,但是运行在 v8 上的 JavaScript 是单线程的。Node 的 child_process 模块用...

复制代码 代码如下:var cp = require;var n = cp.fork(__dirname + '/sub.js');n.on('message', function { console.log('PARENT got message:', m);});n.send;

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 这个文件,所以会看到终端打印出了相关错误信息。

复制代码 代码如下:PARENT got message: { foo: 'bar' }CHILD got message: { hello: 'world' }

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会看到主进程收到了来自子进程的消息,而子进程也收到了来自主进程的消息。

返回顶部