本文内容主要分为两大部分,第一部分是 Node.js 的基础和架构,第二部分是 Node.js 核心模块的实现。

1. Nodejs 组成

Node.js 主要由 V8、Libuv 和第三方库组成:

  1. Libuv:跨平台的异步 IO 库,但它提供的功能不仅仅是 IO,还包括进程、线程、信号、定时器、进程间通信,线程池等。
  2. 第三方库:异步 DNS 解析( cares )、HTTP 解析器(旧版使用 http_parser,新版使用 llhttp)、HTTP2 解析器( nghttp2 )、 解压压缩库( zlib )、加密解密库( openssl )等等。
  3. V8:实现 JS 解析、执行和支持自定义拓展,得益于 V8 支持自定义拓展,才有了 Node.js。

2. Node.js代码架构

上图是 Node.js 的代码架构,Node.js的代码主要分为 JS、C++、C 三种:

  1. JS 是我们平时使用的那些模块(http/fs)。
  2. C++ 代码分为三个部分,第一部分是封装了 Libuv 的功能,第二部分则是不依赖于 Libuv ( crypto 部分 API 使用了 Libuv 线程池),比如 Buffer 模块,第三部分是 V8 的代码。
  3. C 语言层的代码主要是封装了操作系统的功能,比如 TCP、UDP。

了解了 Node.js 的组成和代码架构后,我们看看 Node.js 启动的过程都做了什么。

3. Node.js启动过程

3.1 注册 C++ 模块

首先 Node.js 会调用 registerBuiltinModules 函数注册 C++ 模块,这个函数会调用一系列 registerxxx 的函数,我们发现在 Node.js 源码里找不到这些函数,因为这些函数是在各个 C++ 模块中,通过宏定义实现的,宏展开后就是上图黄色框的内容,每个 registerxxx 函数的作用就是往 C++ 模块的链表了插入一个节点,最后会形成一个链表。

那么 Node.js 里是如何访问这些 C++ 模块的呢?在 Node.js 中,是通过 internalBinding 访问 C++ 模块的,internalBinding 的逻辑很简单,就是根据模块名从模块队列中找到对应模块。但是这个函数只能在 Node.js 内部使用,不能在用户 JS 模块使用,用户可以通过 process.binding 访问 C++ 模块。