原文地址在个人博客,转载请注明出处,谢谢!javascript
node 模块是node 完成强大功能的实现者。node 的核心模块包括events、fs、buffer、stream、cluster、http、net、一些操做OS和工具模块、全局对象等。本文将在node核心特性理解的基础上进一步深刻探讨node核心模块的具体细节。本文主要探讨的模块有:全局对象global及其重要属性、多进程cluster、events重要类EventEmitter、流Stream、文件系统fs、网络http,还会介绍node框架express相关。html
本文先来介绍全局对象global及其重要属性、多进程cluster模块。java
与浏览器对应的window同样,在node中global是全局对象,在全局做用域定义的任何变量都会保存为global的属性,称为全局变量。下面是global一些重要的属性:node
module
、require
、exports
这三个全局变量组成了node 的模块定义和引入,是 commonJS 的实现。node将每一个文件视为一个模块,在执行到每一个模块以前都会定义好上述三个变量,所以能够直接使用。来看它们之间的协做:git
// module1.js exports.fun = function(a,b) { return a + b; } //绑定在exports的属性能够被其余模块引入使用 //module2.js var module1 = require('./module1') module1.fun(3,2) //5
相关机制:github
module
实际上不是全局的,而是每一个模块本地的。module除了exports 还有其余关于模块的属性,例如module.children
module.exports
的简写,表示这个模块的输出。有一点须要注意,对exports直接赋值exports = {...}
并不会被输出,由于exports事先已经被定义了,再次这样赋值会被覆盖,须要带上module:module.exports = {...}
另外,因为V8引擎对ES6的不断支持,node 中也能够直接使用ES6的一些特性、例如promise、class等,ES6的模块也被node 实验性的引入而且是稳定的。详情见nodejs中文网。能够在node.green查看支持的特性shell
异步操做 setTimeout、setInterval、setImmediate、process.nextTickexpress
setTimeout(fn,0)
差很少,都是至关于当即在事件队列末尾插入一个事件,但也有差异。process.nextTick(fn,...args)
表示在当前调用栈结束后,在下一个事件执行前调用回调函数。node 提供这个API是为了把复杂耗时的任务放到最后去处理,以便优先执行简单的任务。来看它们之间的比较:编程
setTimeout(() => console.log("setTimeout0"),0) setImmediate(() => console.log("setImmediate")) process.nextTick(() => console.log("nextTick")) //输出nextTick setTimeout0 setImmediate 或者 nextTick setImmediate setTimeout0
不管process.nextTick
写在什么地方,它老是第一个输出。不管setTimeout和setImmediate谁先谁后,均可能出现两种结果,其中setTimeout(fn,0)先于setImmediate多一点。这是由于它们三个产生的事件推入到了不一样的watcher
(观察者)中—— setTimeout推入到了定时器观察者,setImmediate是check观察者,而process.nextTick()是idle观察者 ,而node主线程在事件循环时调用watcher 的顺序通常是 idle观察者 > check观察者,idle观察者 > 定时器观察者,check 和定时器不分前后,但定时器先于check的几率大一点。api
process 是node对进程的表示,提供了操做进程的接口,能够用process来提供进程有关信息,控制进程 。
process提供的接口包括
描述进程的一些状态(事件):exit、beforeExit、uncaughtException、Signal
进程退出返回的状态码:Uncaught Fatal Exception、Signal Exits、Unused等
进程的相关信息:stdout、stderr、config、stdin、exitCode、pid(进程编号)等
操做进程的方法:abort、chdir、cwd、kill(发送信号给进程)、exit、nextTick、getgid、setgid、uptime等
__filename
、 __dirname
这些都是全局变量,能够在任何地方引用
一个进程只能利用一个CPU时间分片,为了高效利用多核CPU,node 提供了能够建立子进程(注意不是子线程)的child_process
模块,来帮助主进程高效利用多核CPU完成其余复杂的任务。之因此提供建立子进程而不是子线程的接口,是由于这让咱们的程序状态单一,不用在乎状态同步、死锁、上下文切换开销等等多线程编程中的头疼问题。这样以来一个进程只有一个线程。虽然单线程也会带来一些问题,如错误会引发整个应用退出等,但这都有了很好的解决方案。
建立子进程
node有三种建立子进程的接口:
message
事件和调用 send
方法,就能够在父子进程间进行通讯了。父子进程通讯
首先,这三种API都返回ChildProcess
实例,所以均可以经过访问stdout
属性来获得输出,exec/execFile 接口还能够在参数里绑定回调函数拿到子进程的stdout 。
const { exec, execFile, spawn, fork } = require("child_process") // exec/execFile 接口既能够在参数里绑定回调函数拿到输出流,也能够利用返回的ChildProcess实例 const exec_process = exec("node child_process.js", {}, (err, stdout, stderr) => { if (err) { console.log(err) } else if(stdout) { console.log(stdout) } else { console.log(stderr) } }) exec_process.stdout.on('data',(data) => console.log(`${data}`)) // spawn 接口没有回调函数,只能利用返回的ChildProcess实例绑定监听数据函数拿到子进程的输出 const spawn_process = spawn('node',['child_process'], {}) spawn_process.stdout.on('data', (data) => console.log(`${data}`)) //fork 也能够利用返回的ChildProcess实例,注意配置项silent要设为true const fork_process = fork('child_process.js', [], {'silent': true}) fork_process.stdout.on('data' ,(data) => console.log(`${data}`))
其次, fork返回的ChildProcess
实例有一个额外的内置的通讯通道IPC,它容许消息在父进程和子进程之间来回传递。
// child_process.js process.on('message', (data) => { console.log(`message from Parent: ${data}`); }) setTimeout(() => { process.send('send from child'); }, 2000) // parent.js const { fork } = require("child_process") const p = fork( 'child_process.js', // 须要执行的脚本路径 [], // 传递的参数 {} ) p.on('message', data => { //监听子进程消息 console.log(`message from child: ${data}`) }) p.send('send from parent') //发送消息给子进程
cluster
模块是对child_process
模块的进一步封装,专用于解决单进程NodeJS Web服务器没法充分利用多核CPU的问题。使用该模块能够简化多进程服务器程序的开发,让每一个核上运行一个工做进程,并统一经过主进程监听端口和分发请求。 ——七天学会node.js
const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; console.log(numCPUs) if (cluster.isMaster) { console.log(`主进程 ${process.pid} 正在运行`); // 衍生工做进程。 for (let i = 0; i < numCPUs; i++) { cluster.fork(); // 调用了 child_process.fork()方法建立工做进程 } cluster.on('exit', (worker, code, signal) => { console.log(`工做进程 ${worker.process.pid} 已退出`); }); } else { // 工做进程能够共享任何 TCP 链接。 // 在本例子中,共享的是一个 HTTP 服务器。 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); }).listen(8000); console.log(`工做进程 ${process.pid} 已启动`); } console.log("WOW") // 输出(Mac OS) 4 主进程 55570 正在运行 WOW 4 工做进程 55571 已启动 WOW 4 工做进程 55572 已启动 WOW 4 工做进程 55573 已启动 WOW 4 工做进程 55574 已启动 WOW //能够看到 fork 是异步建立的,调用时请求建立进程并当即返回,系统建立好进程后会加入到事件队列,执行到事件就调用回调函数,这个回调函数包括执行一遍这个文件
cluster.fork()
实际调用了child_process.fork()
,所以创建了IPC通道与父进程通讯。它会建立一个进程并返回cluster.worker
实例。建立的每一个进程之间都是独立的,一个进程的开启和关闭不影响其余进程。只要有存活的进程,服务器就能够继续处理链接。
主进程负责监听端口,接收新链接后会自动将链接循环(默认)分发给cluster.fork()
建立的工做进程来帮忙处理,所以可使用cluster模块来实现简单的负载均衡。
注意:
cluster.fork()
返回cluster.worker
实例可能会引发困惑,困惑的缘由把主进程和工做进程作了master和worker的区分,这里不用这么区分,既然主进程也是进程,那么也能够看做worker,调用cluster.fork().send(message)
就能够向子进程发送信息,一样监听信息也是cluster.fork().on('message', (data) => {...})
。cluster.fork()
只有增长进程环境变量的参数(通常是不带的),没有要执行文件路径的参数,所以像上面代码那样主进程作的事和工做进程作的事写在同一文件(if-else语句里)是合理的。