本章介绍node api 中的主要模块,简析每个模块的功能以及定位,大致分为常用模块和进阶模块
常用模块主要包括 基础模块和常用的工具模块
node 遵循commonjs规范,每个文件被视为一个独立模块,通过exports导出模块,通过require引入模块
模块分类
node的模块主要分为四种:
Module 主要结构
模块封装器
可以从上面的Module结构中看到wrapper, 内容为:'(function (exports, require, module, __filename, __dirname) { ', '\n});'
,wrapper就是node的模块封装器结构,它保持了顶层的变量(用 var、 const 或 let 定义)作用在模块范围内,而不是全局对象,并且可以通过exports和module导出一些对象
模块查找
模块查找的规范就是require的实现 ,总结如下
paths: [ 'D:\\selfProject\\letCode\\node\\util-src\\node_modules', 'D:\\selfProject\\letCode\\node\\node_modules', 'D:\\selfProject\\letCode\\node_modules', 'D:\\selfProject\\node_modules', 'D:\\node_modules' ] }
模块管理工具
node的核心模块数量不多,但在其上衍生出来的第三方模块数以百万计,为了解决大量模块管理的问题,相继出现了npm,yarn,pnpm等优秀的工具详见node版本管理,包管理
global是指node中的全局变量的宿主,主要包含以下对象:Buffer,global,timers,console,process,URL,queueMicrotask,TextDecoder,WebAssembly等,可以通过在global上挂载变量来自定义全局变量,如global.config = {“db”:“mysql”}
如上所述,process是一个全局对象,它提供了对当前node进程管理的能力
事件
process 是EventEmitter 的实例,node为process定义了很多事件来捕获进程生命周期内的通信和异常情况,提供给开发者处理,常用的如’exit’,‘message’,‘uncaughtException’,'信号事件’等
属性和方法
process 提供了一些属性和方法,用来获取和管理当前进程的信息,状态。如’process.cwd()’,'process.argv’等。通过这些属性和方法可以进行node命令行脚本开发,进程状态管理等
os 模块提供了与操作系统相关的实用方法和属性,如os.cpus()
,os.freemem()
,os.loadavg()
等,通过这些方法可以获取进程使用资源的情况,一般这些数据都是存储在进程号对应的目录下,也可以通过其他系统工具(如ps,ss)获取
大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener),所有能触发事件的对象都是 EventEmitter 类的实例。
event 原理
node的EventEmitter 是对观察者模式的实现详见:设计模式-观察者模式
什么是观察者模式
观察者模式是这样一种设计模式。一个被称作被观察目标的对象,维护一组被称为观察者的对象,这些观察者依赖于观察目标,被观察者自动将自身的状态的任何变化通知给它们。
它定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
逻辑关系
观察者模式,包括如下角色,Subject: 目标,ConcreteSubject: 具体目标,Observer: 观察者,ConcreteObserver: 具体观察者,关系如下:
event 使用及注意事项
事件回调同步和异步
当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。 被调用的监听器返回的任何值都将会被忽略并丢弃
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('嘤嘤嘤');
});
myEmitter.emit('event');
console.log('111')
但监听器函数可以使用 setImmediate() 和 process.nextTick() 方法切换到异步的操作模式:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
setImmediate(() => {
console.log('异步地发生');
});
});
myEmitter.emit('event');
console.log('111')
避免事件递归
不当的事件递归调用会让程序陷入死循环导致栈溢出
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('嘤嘤嘤');
myEmitter.emit('event');
});
myEmitter.emit('event');
console.log('111')
错误处理
当 EventEmitter 实例出错时,应该触发 ‘error’ 事件。 这些在 Node.js 中被视为特殊情况。如果没有为 ‘error’ 事件注册监听器,则当 ‘error’ 事件触发时,会抛出错误、打印堆栈跟踪、并退出 Node.js 进程。所以应始终为’error’ 事件注册监听器,捕获并处理错误。
延申阅读
前面提到观察者模式,通过建立对象间的一种依赖关系,在一个对象发生改变时自动通知其他对象,其他对象将做出反应。这和平时用到的mq,redis等中间件的发布-订阅很像,也和我们常用的事件委托,信号槽等机制很像,关于这些区别,可以看下面的延申阅读,这里也总结下:
事件委托是观察者模式的一种特殊实现;发布订阅是通过消息代理进行通知,观察对象和观察者之间互相不知道对方的存在,从而实现两者的完全解耦。
node中 timer是全局的API,用于安排函数在未来某个时间点被调用。
Immediate
此对象在 setImmediate() 内部创建并返回, 它可以传给 clearImmediate() 以取消已安排的行动
Timeout
此对象在 setTimeout() 和 setInterval() 内部创建并返回。 它可以传给 clearTimeout() 或 clearInterval() 以取消已安排的行动。
clear 和 unref
Immediate 和 Timeout 对象都有对应的 clear 方法,可用于取消定时器并阻止其触发
Immediate 和 Timeout 都有unref方法,有一些博文将其也解释为取消定时器。实质上,当unref被调用时,活动的定时器对象将不会要求 Node.js 事件循环保持活动状态, 如果没有其他的活动保持事件循环运行,则进程可能会在 定时器 对象的回调被调用之前退出,定时器对象没有被取消,其回调之所以会不被执行,是因为事件循环结束,进程退出了
Error API 是node中进行错误报告的api,错误报告方式有throw机制,回调错误(常说的错误优先),错误事件三种,针对不同的错误报告方式,需要分别采用对应的错误捕获方式,分别为try…catch;错误判断;错误事件捕获 ,除此之外,Error Api 提供了一些ErrorCode 来方便定位错误的范围
之前的process模块提供了对当前运行进程管理的能力,但不能创建新进程,child_process 提供了创建多进程的能力,可以通过这种方式利用单机的多核资源
进程
进程是运行中的程序,是系统资源分配和调度的最小单位;多进程就是对进程进行复制,每一个子进程都拥有独立的用户空间地址,数据栈,进程之间无法访问进程里定义的全局变量,数据结构,只能通过进程间通信(IPC)的方式进行数据共享
用户空间和内核空间
每一个进程都拥有独立的用户空间地址,那什么是用户空间地址呢
现在操作系统都是采用虚拟存储器,对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
进程间通信
进程所拥有的资源都是独立的,那进程之间进行资源共享,需要通过IPC的方式进行数据共享,IPC有一下几种:
创建进程方式
以上sync后缀的同步方法会阻塞Node事件循环,等到进程完全退出后才返回
Buffer是一个典型的Javascript和C++结合的模块,性能相关部分用C++实现,非性能相关部分用javascript实现,Buffer 类是一个全局变量,用于直接处理二进制数据。 它可以使用多种方式构建。同过buffer可以使用V8堆内存之外的原始内存
创建buffer
buffer 提供了 alloc,from,allocUnsafe 三个方法来创建buffer
buffer 编码
buffer和字符串转换时可以指定编码,如果没有指定,默认使用UTF-8作为默认值,node支持的编码如下:
编码本身就是一门神奇的学问,避免编码问题在日常项目中出问题,就要尽可能统一规范,包括机器的采集,系统的选择
内存分配
slab机制先申请再分配,具有如下三种状态
node buffer 在初次加载时就会初始化 1 个 8KB 的内存空间,创建buffer对象时 根据申请的内存大小分为 小 Buffer 对象 和 大 Buffer 对象,如果是小 Buffer,会继续判断这个 slab 空间是否足够,如果空间足够就去使用剩余空间同时更新 slab 分配状态,偏移量会增加;如果空间不足,slab 空间不足,就会去创建一个新的 slab 空间用来分配。大 Buffer 情况,则会直接调用 createUnsafeBuffer(size) 函数,不论是小 Buffer 对象还是大 Buffer 对象,内存分配是在 C++ 层面完成,内存管理在 JavaScript 层面,最终还是可以被 V8 的垃圾回收标记所回收
内核内存分配延申阅读:
碎片整理
内存分配和释放的过程中,会产生内存碎片,内存碎片即“碎片的内存”描述一个系统中所有不可用的空闲内存,这些碎片之所以不能被使用,是因为负责动态分配内存的分配算法使得这些空闲的内存无法使用。所以需要对这些碎片内存进行整理,
使用场景
流是对输入输出设备的抽象,具有方向性,通过流可以方便的处理二进制数据
流分类
流应用
node提供了fs模块用于文件操作,而该模块常见的api均基于内存直接存取文件,对于大文件进行这样的操作会导致进程崩溃,所以fs又提供了基于流操作文件的类,这样可以通过缓冲(背压,流控)解决大文件读写的问题
http 模块中req,和 res 对象基于流操作
net 模块用于创建基于流的 TCP 或 IPC 的服务器(net.createServer())
与客户端(net.createConnection())
文件加解密,解压缩
继承自转换流 的zlib ,crypto
模块
自定义流
browserify gulp webpack
等工具中的流处理 详见
fs模块提供了与文件系统进行交互的API,node提供了同步和异步两种API,由于同步API会阻塞进程,所以我们应该尽可能在生产使用异步API
api
fs分别提供了同步和异步操作文件系统的api,也提供了对流对象的封装,通过流的方式操作文件,成为文件流,node不断迭代的过程中,也逐步提供了promise化api
文件系统
文件系统是一套实现了数据的存储、分级组织、存取和获取等操作的抽象数据类型;虚拟文件系统是对文件系统的抽象,屏蔽了不同文件系统协议的差异,方便用户程序调用操作文件系统
IO模型
IO操作是应用程序通过向内核发起系统调用完成对I/O的间接访问,一次IO包括两个阶段:
应用程序发起IO调用到内核执行IO返回之前,应用进程/线程所处的状态可能是阻塞或非阻塞的,根据此状态将IO区分为阻塞IO,非阻塞IO,所以阻塞和非阻塞关心的是应用程序发起内核调用是否立即返回
阻塞IO是指发起内核调用后,用户进程一直阻塞等待数据返回,而不进行其他操作
但应用程序阻塞等待,会浪费cpu资源
非阻塞IO是指发起内核调用后,内核收到请求后立即返回,用户进程通过轮询的方式获取内核的处理结果。
但是轮询依旧需要进行系统调用,高并发下,频繁无效的系统调用会浪费cpu性能
IO多路复用是内核提供的能力,可以同时等待多个IO描述符,其中一个数据准备就绪后(此处阻塞),就可以返回,有select/poll/epoll 三种实现方式
信号驱动IO在系统调用发起后,会注册一个信号,本次调用立即返回,内核中的数据准备好之后,会立即给应用程序发送信号,此时应用程序再发起系统调用复制数据
异步IO将所有工作都交给内核,应用程序发起系统调用后,立即返回,内核会准备好数据并完成数据拷贝,此时再通知应用进程IO已完成,在此期间应用进程和内核之间是并行的
总结
net 模块提供了创建TCP通信 或 IPC的能力,node中的http模块基于net模块实现
net组成
server 提供创建TCP服务端的能力
socket 提供创建TCP客户端的能力
网络模型
iso 七层, tcp/ip 4层
TCP连接
延伸阅读
dgram 模块提供了 UDP 数据包 socket 的实现
UDP
udp是无连接,不可靠的简易的数据传输协议,工作在传输层。由于其不需要建立连接,不保证数据传输的可靠性,所以无需复杂的三次握手,四次握手,拥塞控制等处理过程
dgram 创建服务端
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.log(`服务器异常:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`服务器接收到来自 ${rinfo.address}:${rinfo.port} 的 ${msg}`);
});
server.on('listening', () => {
const address = server.address();
console.log(`服务器监听 ${address.address}:${address.port}`);
});
server.bind(41234);
const dgram = require('dgram');
const buf1 = Buffer.from('node');
const buf2 = Buffer.from('test');
const client = dgram.createSocket('udp4');
client.send([buf1, buf2], 41234, (err) => {
client.close();
});
socket应用
socket编程可以建立长连接,完成点对点即时通信(IM)和实时通信(RTC)的场景的需求
socket.io
socket.io 是一个为实时应用提供跨平台实时通信的库。socket.io 旨在使实时应用在每个浏览器和移动设备上成为可能,模糊不同的传输机制之间的差异。
http相关的模块提供了基于Http协议通信的API,将消息解析为消息头和消息体
http
HTTP 超文本传输协议是位于 TCP/IP 体系结构中的应用层协议,它是万维网的数据通信的基础。
https
HTTPS 是基于 TLS/SSL 的 HTTP 协议。在 Node.js 中,其被实现为一个单独的模块。
Agent 管理Https客户端连接的重用和持久性
Server 继承自 tls.Server,管理https 服务端的连接
http 协议报文
https 加解密原理
https 结合了非对称加密和对称加密,通过非对称加密验证证书的可靠性,并完成对称加密 密钥的生成和传递,然后通过密钥进行对称加密的通信
非对称加密的过程如图
http2 简介
HTTP/2 是 HTTP/1.x 的扩展,TTP 2.0 增加了新的二进制分帧数据层,而这一层并不兼容之前的 HTTP 1.x。
querystring 模块提供用于解析和格式化 URL 查询字符串的实用工具
path 模块提供了一些实用工具,用于处理文件和目录的路径,封装了linux和windows平台差异
url 模块用于处理与解析 URL,具体解析的url结构如图
crypto 模块提供了加密功能,包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。
加密是为了防止信息泄漏(比如密码),签名是为了防止信息被篡改(比如交易金额)
util 模块用于支持 Node.js 内部 API 的需求
主要Api
console 模块提供了一个简单的调试控制台,类似于 Web 浏览器提供的 JavaScript 控制台。
console.log默认输出到process.stdout(可以通过Console指定输出的流),由于stdout流在各平台上实现的,导致console.log的输出可能是异步或者同步的,写操作是否为同步,取决于连接的是什么流以及操作系统是 Windows 还是 POSIX :
部分图片来自网络文章
本文地址:https://blog.csdn.net/u014636124/article/details/107204531
如对本文有疑问, 点击进行留言回复!!
[Node.js] mySQL数据库 -- 英雄英雄管理系统接口
javascript 实现Promise.prototype.then
网友评论