本文内容主要分为两大部分,第一部分是 Node.js 的基础和架构,第二部分是 Node.js 核心模块的实现。
- 一 Node.js 基础和架构
- Node.js 的组成
- Node.js 代码架构
- Node.js 启动过程
- Node.js 事件循环
- 二 Node.js 核心模块的实现
- 进程和进程间通信
- 线程和线程间通信
- Cluster
- Libuv 线程池
- 信号处理
- 文件
- TCP
- UDP
- DNS
1. Nodejs 组成
Node.js 主要由 V8、Libuv 和第三方库组成:
- Libuv:跨平台的异步 IO 库,但它提供的功能不仅仅是 IO,还包括进程、线程、信号、定时器、进程间通信,线程池等。
- 第三方库:异步 DNS 解析( cares )、HTTP 解析器(旧版使用 http_parser,新版使用 llhttp)、HTTP2 解析器( nghttp2 )、 解压压缩库( zlib )、加密解密库( openssl )等等。
- V8:实现 JS 解析、执行和支持自定义拓展,得益于 V8 支持自定义拓展,才有了 Node.js。
2. Node.js代码架构
上图是 Node.js 的代码架构,Node.js的代码主要分为 JS、C++、C 三种:
- JS 是我们平时使用的那些模块(http/fs)。
- C++ 代码分为三个部分,第一部分是封装了 Libuv 的功能,第二部分则是不依赖于 Libuv ( crypto 部分 API 使用了 Libuv 线程池),比如 Buffer 模块,第三部分是 V8 的代码。
- 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++ 模块。