Node使用的事件驱动编程:


事件驱动模型主要包含3个对象事件源事件和事件处理程序


  •   事件源产生事件的地方(html元素)
  •   事件点击/鼠标操作/键盘操作等等
  •   事件对象当某个事件发生时可能会产生一个事件对象该时间对象会封装好该事件的信息传递给事件处理程序
  •   事件处理程序响应用户事件的代码 


Node.js的单线程并不是真正的单线程只是开启了单个线程进行业务处理(cpu的运算),同时开启了其他线程专门处理I/O。当一个指令到达主线程主线程发现有I/O之后直接把这个事件传给I/O线程不会等待I/O结束后再去处理下面的业务而是拿到一个状态后立即往下走这就是单线程”、“异步I/O”

I/O操作完之后会有一个回调事件这个事件会放在一个事件处理队列里头在进程启动时node会创建一个event loop的循环它的每一次轮询都会去查看是否有事件需要处理是否有事件关联的回调函数需要处理如果有就处理然后加入下一个轮询如果没有就退出进程这就是所谓的事件驱动”。这也从Node的角度解释了什么是事件驱动”。


NODE的架构:

模块角度


分为了三大块:
  • your code 为编辑代码
  • node.js 核心
  • Host environment 为宿主环境(提供各种服务,如文件管理,多线程,多进程,IO etc)
其中重点 很显然是nodejs
v8 engine
主要有两个作用 
1.虚拟机的功能,执行js代码(自己的代码,第三方的代码和native modules的代码)。
 2.提供C++函数接口,为nodejs提供v8初始化,创建context,scope等。

libuv
它是基于事件驱动的异步IO模型库,我们的js代码发出请求,最终由libuv完成,
而我们所设置的回调函数同样是在libuv触发。
说白了, libuv负责异步调用工作(event loop)
builtin modules
c++写成的模块
包含了crypto,zlib, file stream etc 基础功能。
(v8提供了函数接口,libuv提供异步IO模型库,以及一些nodejs函数,为builtin modules提供服务)
native modules
它是由js写成的模块
同时这些模块又依赖builtin modules来获取相应的服务支持
简单总结:
如果把nodejs看做一个黑匣子,它暴露给开发者的接口则是native modules
当我们发起请求时,请求自上而下,穿越native modules,通过builtin modules将请求传送至v8,libuv和其他辅助服务
请求结束,则从下回溯至上,最终调用我们的回调函数。

当我们执行node xxx.js的时候,node会先做一些v8初试化,libuv启动的工作,然后交由v8来执行native modules以及我们的js代码。

也可以说:
Nodejs就是由由v8 engine,libuv和内置模块(c++写的和js写的两种)组成的,可以将v8 engine和 libuv看成一个库,两者是以源码的方式直接编译执行node中去的。接下来再详细的分析一下这三个部分:

V8 

        #include "include/v8.h"
    #include "include/libplatform/libplatform.h"
 
    using namespace v8;
 
    int main(int argc, char* argv[]) {
    // V8初试化.
    V8::InitializeICU();
    Platform* platform = platform::CreateDefaultPlatform();
    V8::InitializePlatform(platform);
    V8::Initialize();
 
    // 创建isolate.
    Isolate* isolate = Isolate::New();
    {
        Isolate::Scope isolate_scope(isolate);
 
        // 创建HandleScope.
        HandleScope handle_scope(isolate);
 
        // 创建context 环境.
        Local<Context> context = Context::New(isolate);
   
        // 引入环境.
        Context::Scope context_scope(context);
   
        // 创建字符串.
        Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World!'");
   
        // 编译字符串.
        Local<Script> script = Script::Compile(source);
   
        // Run,并且获取返回值.
        Local<Value> result = script->Run();
   
        // 转换为utf8,并且打印出来.
        String::Utf8Value utf8(result);
        printf("%s\n", *utf8);
    }
 
    // 关掉v8.
    isolate->Dispose();
    V8::Dispose();
    V8::ShutdownPlatform();
    delete platform;
    return 0;
}
isolate 代表一个V8实例,各个isolate是独立的(隔绝的),isolate中的obj无法再另一个isolate中被使用。
context 代表执行js代码的虚拟机,在这个虚拟机中,集成了一些功能,例如math,json,date(),RegExp()等。一个isolate里可以同时存在多个context,这些context可以自由切换。如下图所示。

在V8 engine中,通过handle访问存在与heap上的js obj,如local,persistent,eternal,在V8中,有一个handle stack用于管理这些handles,如下图所示。

libuv

libuv主要用于处理事件驱动的异步IO模型:http://nikhilm.github.io/uvbook/

libuv通过epoll,kqueue,event ports和IOCP来实现异步network IO。file,dns的操作则依赖于thread pool 来实现,正如上图所示。

上图中的代码分为两个部分:

1. server.listen()是用来创建TCP server时,通常放在最后一步执行的代码。主要指定服务器工作的端口以及回调函数。
2. fs.open()是用异步的方式打开一个文件。

右半部分又分为两个部分:
1. 主线程:主线程也是node启动时执行的线程。node启动时,会完成一系列的初始化动作,启动V8 engine,进入下一个循环。
2. 线程池:线程池的数量可以通过环境变量UV_THREADPOOL_SIZE配置,最大不超过128个,默认为4个。


libuv的重点是IO和event loop,loop负责调度callback,运行idle function,执行轮询等操作。

模块

Nodejs本身也做了很多的工作,它提供了js模块,c++模块等核心模块供开发者使用。
Nodejs 将这些核心js模块命名为native module,c++模块命名为builtin module

当 node test.js 执行时,node_main.cc首先被调用,这一步初始化了V8 engine和libuv执行环境。
uv_run(env->event_loop,UV_RUN_ONCE)用于启动libuv event loop,而我们的js代码则会被传递到creatEnvironment(),最后交由V8处理。
在js中,类似基于prototype继承的方式来实现的,通过V8提供的c++ API来完成这些操作。

函数角度:

以建立http server为例

v8执行js代码 server.listen()时,会通过一些基础服务到TCPWrap::listen(),TCPWrap是nodejs的內建模块,其通过libuv的api uv_listen()的方式,由libuv来完成异步调用。
图中1,2,3,4,5步骤标明了调用和返回的路径
这几步都是同步调用,始于server.listen() 停于第5步 
留下callback TCPWrap::OnConnection()等着所需要的数据准备好后被调用。
libuv在得到所需要的请求后,发起一个逆向的callback,始于libuv中的event loop,终于server中设置的callback函数
而这时,V8可能已经完成了多条任务
可以看到Node.js做的工作像是一座桥。左手V8,右手libuv,将2者有机连接在一起

分析一个http serve的例子:
var http=require("http");
http.createServer(function(req,res){ 
res.writeHead(200,{ "content-type":"text/plain" }); 
res.write("hello nodejs"); 
res.end(); 
}).listen(3000);
结合下面这张图片分析:

从上至下的看:
第一部分:创建一个server实例,这部分还是集中在js部分,其中http.js,http_server.js以及net.js为Nodejs库中的代码,这部分比较重要的细节是net.js,集成了若干API,这些API会为之后提供服务。
第二部分:从调用this.listen()开始。在这一部分,直接调用net.js提供的各种api,reateTCP()通过 process.binding('tcp_wrap').TCP 进入到Nodejs C/C++部分,也就是 TCPWrap部分。在回来net.js的倒数第二步,handle.open(fd),这部分代码最终调用到C/C++部分的TCPWrap::Open()。

重点分析一下TCPWrap这个模块:
在执行函数createTCP()将会调用process.binding('tcp_wrap'),在tcp_wrap模块真正导出的是TCP函数

流程如下图所示: