惭愧惭愧,node.js已经火了好几年了,最近才开始接触……主要是工做中也基本用不到,可是我以为一个东西火确定自有道理,颇有必要接触了解,甚至深刻学习里面的精髓~
官网:https://nodejs.org
API查询:https://nodejs.org/apijavascript
JavaScript是一种运行在浏览器的脚本,它简单,轻巧,易于编辑,这种脚本一般用于浏览器的前端编程,可是一位开发者Ryan有一天发现这种前端式的脚本语言能够运行在服务器上的时候,一场席卷全球的风暴就开始了。php
Node.js是一个基于Chrome JavaScript运行时创建的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,很是适合在分布式设备上运行的数据密集型的实时应用。html
Node是一个Javascript运行环境(runtime)。实际上它是对Google V8引擎进行了封装。V8引 擎执行Javascript的速度很是快,性能很是好。Node对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。前端
Node.js中代码是单进程、单线程执行,可是底层实际上是有线程池的。
NodeJS适合运用在高并发、I/O密集、少许业务逻辑的场景,例如RESTful API,消息推送,聊天服务等java
因为我使用的是Ubuntu Kylin,因此安装方式以下:node
1.curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash
2.sudo apt-get install -y nodejslinux
顺便下载安装一个Atom编辑器来配合敲代码~nginx
curl -sL https://atom.io/download/deb -o atom.deb
#因为atom安装依赖于git,还要先安装git
sudo apt-get install git
sudo dpkg –install atom.debgit
安装完成输入atom运行便可(插件什么的自行https://atom.io/packages寻找)。web
var http = require('http');
http.createServer(function(request, response){
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200,{'Content-Type':'text/plain'});
// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(8888);
// 终端打印以下信息
console.log('Server running at http://127.0.0.1:8888/');
node server.js
浏览器浏览127.0.0.1:8888
NPM是随同NodeJS一块儿安装的包管理工具,能解决NodeJS代码部署上的不少问题,常见的使用场景有如下几种:
npm 的包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差异只是有没有-g而已,好比
npm install express # 本地安装
npm install express -g # 全局安装
本地安装
全局安装
使用npm help可查看全部命令。
Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,相似 Window 系统的终端或 Unix/Linux shell,咱们能够在终端中输入命令,并接收系统的响应。
你能够将数据存储在变量中,并在你须要的使用它。
变量声明须要使用 var 关键字,若是没有使用 var 关键字变量会直接打印出来。
使用 var 关键字的变量可使用 console.log() 来输出变量。
$ node
> x = 10
10
> var y = 10
undefined
> x + y
20
> console.log(“Hello World”)
Hello World
undefined
> console.log(“www.runoob.com”)
www.runoob.com
undefined
Node REPL 支持输入多行表达式,这就有点相似 JavaScript。接下来让咱们来执行一个 do-while 循环:
$ node
> var x = 0
undefined
> do {
… x++;
… console.log(“x: ” + x);
… } while ( x < 5 );
x: 1
x: 2
x: 3
x: 4
x: 5
undefined
>
… 三个点的符号是系统自动生成的,你回车换行后便可。Node 会自动检测是否为连续的表达式。
你可使用下划线(_)获取表达式的运算结果:
$ node
> var x = 10
undefined
> var y = 20
undefined
> x + y
30
> var sum = _
undefined
> console.log(sum)
30
undefined
>
Node.js 异步编程的直接体现就是回调。
异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。
回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 全部 API 都支持回调函数。
例如,咱们能够一边读取文件,一边执行其余命令,在文件读取完成后,咱们将文件内容做为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操做。这就大大提升了 Node.js 的性能,能够处理大量的并发请求。
var fs = require("fs");
//阻塞代码实例
var data = fs.readFileSync('input.txt');
非阻塞代码实例
fs.readFile('input.txt',function(err, data){
if(err)return console.error(err);
console.log(data.toString());
});
Node.js 是单进程单线程应用程序,可是经过事件和回调支持并发,因此性能很是高。
Node.js 的每个 API 都是异步的,并做为一个独立线程运行,使用异步函数调用,并处理并发。
Node.js 基本上全部的事件机制都是用设计模式中观察者模式实现。
Node.js 单线程相似进入一个while(true)的事件循环,直到没有事件观察者退出,每一个异步事件都生成一个事件观察者,若是有事件发生就调用该回调函数.
Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭而后进行处理,而后去服务下一个web请求。
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
这个模型很是高效可扩展性很是强,由于webserver一直接受请求而不等待任何读写操做。(这也被称之为非阻塞式IO或者事件驱动IO)
在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。
// 引入 events 模块
var events = require('events');
// 建立 eventEmitter 对象
var eventEmitter =new events.EventEmitter();
// 建立事件处理程序
var connectHandler =function connected(){
console.log('链接成功。');
// 触发 data_received 事件
eventEmitter.emit('data_received');
}
// 绑定 connection 事件处理程序
eventEmitter.on('connection', connectHandler);
// 使用匿名函数绑定 data_received 事件
eventEmitter.on('data_received',function(){
console.log('数据接收成功。');
});
// 触发 connection 事件
eventEmitter.emit('connection');
console.log("程序执行完毕。");
咱们执行以上代码:
$ node main.js
链接成功。
数据接收成功。
程序执行完毕。
Node.js 全部的非阻塞I/O I/O 操做在完成时都会发送一个事件到事件队列。
Node.js里面的许多对象都会分发事件:一个net.Server对象会在每次有新链接时分发一个事件, 一个fs.readStream对象会在文件被打开的时候发出一个事件。 全部这些产生事件的对象都是 events.EventEmitter 的实例。
EventEmitter 的核心就是事件触发与事件监听器功能的封装。
EventEmitter 的每一个事件由一个事件名和若干个参数组成,事件名是一个字符串,一般表达必定的语义。对于每一个事件,EventEmitter 支持 若干个事件监听器。
当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数做为回调函数参数传递。
var events = require('events');
var emitter =new events.EventEmitter();
emitter.on('someEvent',function(arg1, arg2){
console.log('listener1', arg1, arg2);
});
emitter.on('someEvent',function(arg1, arg2){
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent','arg1 参数','arg2 参数');
EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,咱们在遇到 异常的时候一般会触发 error 事件。
当 error 被触发时,EventEmitter 规定若是没有响 应的监听器,Node.js 会把它看成异常,退出程序并输出错误信息。
咱们通常要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。
大多数时候咱们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。
为何要这样作呢?缘由有两点:
首先,具备某个实体功能的对象实现事件符合语义, 事件的监听和发射应该是一个对象的方法。
其次 JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流或文件流时,必须使用到二进制数据。所以在 Node.js中,定义了一个 Buffer 类,该类用来建立一个专门存放二进制数据的缓存区。
在 Node.js 中,Buffer 类是随 Node 内核一块儿发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可让 Node.js 处理二进制数据,每当须要在 Node.js 中处理I/O操做中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 相似于一个整数数组,但它对应于 V8 堆内存以外的一块原始内存。
utf-8 是默认的编码方式,此外它一样支持如下编码:”ascii”, “utf8”, “utf16le”, “ucs2”, “base64” 和 “hex”。
全部的 Stream 对象都是 EventEmitter 的实例。经常使用的事件有:
为了让Node.js的文件能够相互调用,Node.js提供了一个简单的模块系统。
模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件多是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。
Node.js 提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
接下来咱们就来建立hello.js文件,代码以下:
exports.world =function(){
console.log('Hello World');
}
在以上示例中,hello.js 经过 exports 对象把 world 做为模块的访问接口,在 main.js 中经过 require(‘./hello’) 加载这个模块,而后就能够直接访 问 hello.js 中 exports 对象的成员函数了。
有时候咱们只是想把一个对象封装到模块中
例如:
//hello.js
functionHello(){
var name;
this.setName =function(thyName){
name = thyName;
};
this.sayHello =function(){
console.log('Hello '+ name);
};
};
module.exports =Hello;
这样就能够直接得到这个对象了:
//main.js
varHello= require('./hello');
hello =newHello();
hello.setName('BYVoid');
hello.sayHello();
模块接口的惟一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象自己,而不是原先的 exports。
因为Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,可是内部的加载倒是十分复杂的,其加载优先级也各自不一样。以下图所示:
nodejs-require
尽管原生模块与文件模块的优先级不一样,可是都不会优先于从文件模块的缓存中加载已经存在的模块。
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名以后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个http/http.js/http.node/http.json文件,require(“http”)都不会从这些文件中加载,而是从原生模块中加载。
原生模块也有一个缓存区,一样也是优先从缓存区加载。若是缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
当文件模块缓存中不存在,并且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程当中的包装和编译细节在前一节中已经介绍过,这里咱们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。
require方法接受如下几种参数的传递:
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其全部属性均可以在程序的任何地方访问,即全局变量。
在浏览器 JavaScript 中,一般 window 是全局对象, 而 Node.js 中的全局对象是 global,全部全局变量(除了 global 自己之外)都是 global 对象的属性。
在 Node.js 咱们能够直接访问到 global 的属性,而不须要在应用中包含它。
global 最根本的做用是做为全局变量的宿主。按照 ECMAScript 的定义,知足如下条 件的变量是全局变量:
咱们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于咱们在多核 cpu 的系统上建立多个子进程,从而提升性能。
每一个子进程老是带有三个流对象:child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流,或者也能够是独立的被导流的流对象。
Node 提供了 child_process 模块来建立子进程,方法有:
咱们听到Node.js时,咱们经常会听到异步,非阻塞,回调,事件这些词语混合在一块儿。其中,异步与非阻塞听起来彷佛是同一回事。从实际效果的角度说,异步和非阻塞都达到了咱们并行I/O的目的。可是从计算机内核I/O而言,异步/同步和阻塞/非阻塞实际上时两回事。
I/O的阻塞与非阻塞
阻塞模式的I/O会形成应用程序等待,直到I/O完成。同时操做系统也支持将I/O操做设置为非阻塞模式,这时应用程序的调用将可能在没有拿到真正数据时就当即返回了,为此应用程序须要屡次调用才能确认I/O操做彻底完成。
I/O的同步与异步
I/O的同步与异步出如今应用程序中。若是作阻塞I/O调用,应用程序等待调用的完成的过程就是一种同步情况。相反,I/O为非阻塞模式时,应用程序则是异步的。
当进行非阻塞I/O调用时,要读到完整的数据,应用程序须要进行屡次轮询,才能确保读取数据完成,以进行下一步的操做。
轮询技术的缺点在于应用程序要主动调用,会形成占用较多CPU时间片,性能较为低下。现存的轮询技术有如下这些:
read是性能最低的一种,它经过重复调用来检查I/O的状态来完成完整数据读取。select是一种改进方案,经过对文件描述符上的事件状态来进行判断。操做系统还提供了poll、epoll等多路复用技术来提升性能。
轮询技术知足了异步I/O确保获取完整数据的保证。可是对于应用程序而言,它仍然只能算时一种同步,由于应用程序仍然须要主动去判断I/O的状态,依旧花费了不少CPU时间来等待。
上一种方法重复调用read进行轮询直到最终成功,用户程序会占用较多CPU,性能较为低下。而实际上操做系统提供了select方法来代替这种重复read轮询进行状态判断。select内部经过检查文件描述符上的事件状态来进行判断数据是否彻底读取。可是对于应用程序而言它仍然只能算是一种同步,由于应用程序仍然须要主动去判断I/O的状态,依旧花费了不少CPU时间等待,select也是一种轮询。
理想的异步I/O应该是应用程序发起异步调用,而不须要进行轮询,进而处理下一个任务,只需在I/O完成后经过信号或是回调将数据传递给应用程序便可。
幸运的是,在Linux下存在一种这种方式,它原生提供了一种异步非阻塞I/O方式(AIO)便是经过信号或回调来传递数据的。
不幸的是,只有Linux下有这么一种支持,并且还有缺陷(AIO仅支持内核I/O中的O_DIRECT方式读取,致使没法利用系统缓存。参见:http://forum.nginx.org/read.php?2,113524,113587#msg-113587
以上都是基于非阻塞I/O进行的设定。另外一种理想的异步I/O是采用阻塞I/O,但加入多线程,将I/O操做分到多个线程上,利用线程之间的通讯来模拟异步。Glibc的AIO即是这样的典型http://www.ibm.com/developerworks/linux/library/l-async/。然而遗憾在于,它存在一些难以忍受的缺陷和bug。能够简单的概述为:Linux平台下没有完美的异步I/O支持。
所幸的是,libev的做者Marc Alexander Lehmann从新实现了一个异步I/O的库:libeio。libeio实质依然是采用线程池与阻塞I/O模拟出来的异步I/O。
那么在Windows平台下的情况如何呢?而实际上,Windows有一种独有的内核异步IO方案:IOCP。IOCP的思路是真正的异步I/O方案,调用异步方法,而后等待I/O完成通知。IOCP内部依旧是经过线程实现,不一样在于这些线程由系统内核接手管理。IOCP的异步模型与Node.js的异步调用模型已经十分近似。
以上两种方案则正是Node.js选择的异步I/O方案。因为Windows平台和*nix平台的差别,Node.js提供了libuv来做为抽象封装层,使得全部平台兼容性的判断都由这一层次来完成,保证上层的Node.js与下层的libeio/libev及IOCP之间各自独立。Node.js在编译期间会判断平台条件,选择性编译unix目录或是win目录下的源文件到目标程序中。
下文咱们将经过解释Windows下Node.js异步I/O(IOCP)的简单例子来探寻一下从JavaScript代码到系统内核之间都发生了什么。
不少同窗在碰见Node.js后必然产生过对回调函数究竟如何被调用产生过好奇。在文件I/O这一块与普通的业务逻辑的回调函数不一样在于它不是由咱们本身的代码所触发,而是系统调用结束后,由系统触发的。下面咱们以最简单的fs.open方法来做为例子,探索Node.js与底层之间是如何执行异步I/O调用和回调函数到底是如何被调用执行的。
fs.open =function(path, flags, mode, callback){
callback = arguments[arguments.length -1];
if(typeof(callback)!=='function'){
callback = noop;
}
mode = modeNum(mode,438/*=0666*/);
binding.open(pathModule._makeLong(path),
stringToFlags(flags),
mode,
callback);
};
fs.open的做用是根据指定路径和参数,去打开一个文件,从而获得一个文件描述符,是后续全部I/O操做的初始操做。
在JavaScript层面上调用的fs.open方法最终都透过node_file.cc调用到了libuv中的uv_fs_open方法,这里libuv做为封装层,分别写了两个平台下的代码实现,编译以后,只会存在一种实现被调用。
在uv_fs_open的调用过程当中,Node.js建立了一个FSReqWrap请求对象。从JavaScript传入的参数和当前方法都被封装在这个请求对象中,其中回调函数则被设置在这个对象的oncomplete_sym属性上。
req_wrap->object_->Set(oncomplete_sym, callback);
对象包装完毕后,调用QueueUserWorkItem方法将这个FSReqWrap对象推入线程池中等待执行。
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTELONGFUNCTION)
QueueUserWorkItem接受三个参数,第一个是要执行的方法,第二个是方法的上下文,第三个是执行的标志。当线程池中有可用线程的时候调用uv_fs_thread_proc方法执行。该方法会根据传入的类型调用相应的底层函数,以uv_fs_open为例,实际会调用到fs__open方法。调用完毕以后,会将获取的结果设置在req->result上。而后调用PostQueuedCompletionStatus通知咱们的IOCP对象操做已经完成。
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped))
PostQueuedCompletionStatus方法的做用是向建立的IOCP上相关的线程通讯,线程根据执行情况和传入的参数断定退出。
至此,由JavaScript层面发起的异步调用第一阶段就此结束。
在调用uv_fs_open方法的过程当中实际上应用到了事件循环。以在Windows平台下的实现中,启动Node.js时,便建立了一个基于IOCP的事件循环loop,并一直处于执行状态。
uv_run(uv_default_loop());
每次循环中,它会调用IOCP相关的GetQueuedCompletionStatus方法检查是否线程池中有执行完的请求,若是存在,poll操做会将请求对象加入到loop的pending_reqs_tail属性上。 另外一边这个循环也会不断检查loop对象上的pending_reqs_tail引用,若是有可用的请求对象,就取出请求对象的result属性做为结果传递给oncomplete_sym执行,以此达到调用JavaScript中传入的回调函数的目的。 至此,整个异步I/O的流程完成结束。其流程以下:
事件循环和请求对象构成了Node.js的异步I/O模型的两个基本元素,这也是典型的消费者生产者场景。在Windows下经过IOCP的GetQueuedCompletionStatus、PostQueuedCompletionStatus、QueueUserWorkItem方法与事件循环实。对于*nix平台下,这个流程的不一样之处在与实现这些功能的方法是由libeio和libev提供。
http://www.runoob.com/nodejs/nodejs-tutorial.html
http://www.infoq.com/cn/articles/nodejs-asynchronous-io/