Node.js 权威指南笔记(一)
一、Node.js 介绍
- 实现高性能服务器
- 非阻塞型 I/O 及事件环机制
- 异步
- 回调函数
- 适合场景:
- 聊天服务器
- 综合服务类网站或电子商务网站的服务器
- 模块 exports
二、Node.js 基础知识
- 事件处理机制与事件环机制
- 全局变量及全局函数
- 自定义事件、事件分发
- node-inspector 调试工具
三、模块语 npm 包管理工具
- 模块的概念
- 开发应用程序时组织及管理模块
- 第三方提供的各种包/模块
四、使用 Buffer 类处理二进制数据
- 处理 TCP 流或文件流时,必须要处理二进制数据
- Buffer 类,用来创建一个专门存放二进制数据的缓存区
- Buffer 类、对象的常见概念以及知识点
- Buffer 对象与字符串对象之间的相互转换
- toString 方法
- write 方法
- Buffer 对象与数值对象之间的相互转换
- buf.readUInt8
- buf.writeUInt8
- ...
- Buffer 对象与 JSON 对象之间的相互转换
- JSON.stringify
- JSON.parse
- ...
- Buffer 对象中保存的二进制数据复制到另一个 Buffer 对象中
- copy
五、在 Node.js 中操作文件系统
fs
模块path
模块
六、实现基于 TCP 与 UDP 的数据通信
net
模块dgram
模块
七、创建 HTTP 与 HTTPS 服务器及客户端
http
模块https
模块
1. 基础概念:HTTP 和 HTTPS 模块是什么?
http
模块:Node.js 提供的用于处理 HTTP 请求和响应的模块。它允许我们创建一个 HTTP 服务器(例如,一个网站或 API 的后端),并且还可以发起 HTTP 客户端请求(例如从另一个服务器获取数据)。https
模块:HTTPS 是 HTTP 的加密版本。https
模块和http
模块类似,但它处理的是通过 SSL/TLS 加密的请求和响应,这样确保数据在客户端与服务器之间传输时是安全的。
2. 创建 HTTP 和 HTTPS 服务器及客户端
HTTP 服务器
首先,创建一个最基本的 HTTP 服务器:
// 引入 http 模块
const http = require('http')
// 创建服务器
const server = http.createServer((req, res) => {
res.statusCode = 200 // 状态码 200 表示成功
res.setHeader('Content-Type', 'text/plain') // 设置响应头
res.end('Hello World\n') // 返回响应内容
})
// 监听端口 3000
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000')
})
流程解析:
http.createServer
创建一个 HTTP 服务器,传入一个回调函数,回调函数接收两个参数:请求对象req
和响应对象res
。res.end()
方法结束响应并发送给客户端。server.listen(3000)
表示服务器监听 3000 端口。
HTTPS 服务器
创建 HTTPS 服务器时,需要 SSL/TLS 证书和密钥。这些通常由认证机构(CA)颁发,也可以使用自签名证书进行测试。
// 引入 https 模块和 fs 模块
const https = require('https')
const fs = require('fs')
// 读取 SSL 证书和密钥
const options = {
key: fs.readFileSync('path/to/private.key'),
cert: fs.readFileSync('path/to/certificate.crt'),
}
// 创建 HTTPS 服务器
const server = https.createServer(options, (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello Secure World\n')
})
// 监听 8443 端口
server.listen(8443, () => {
console.log('服务器运行在 https://localhost:8443')
})
流程解析:
- 需要一个包含密钥(
key
)和证书(cert
)的配置对象options
。 https.createServer(options, callback)
创建一个 HTTPS 服务器,类似于 HTTP,但它是通过加密连接进行通信的。- 使用
server.listen(8443)
监听 HTTPS 的端口号(8443 是常用的 HTTPS 端口)。
3. 创建 HTTP 和 HTTPS 客户端
HTTP 客户端
HTTP 客户端用于发送请求并获取数据。可以使用 http.request
方法:
const http = require('http')
// 发送 HTTP GET 请求
const options = {
hostname: 'example.com',
port: 80,
path: '/',
method: 'GET',
}
const req = http.request(options, (res) => {
let data = ''
// 接收数据
res.on('data', (chunk) => {
data += chunk
})
// 数据接收完成
res.on('end', () => {
console.log(data)
})
})
// 处理错误
req.on('error', (error) => {
console.error(`请求遇到问题: ${error.message}`)
})
// 结束请求
req.end()
流程解析:
- 使用
http.request
发起 HTTP 请求。传入的options
包含了主机名、端口、路径和请求方法。 res.on('data')
用于接收服务器返回的数据。res.on('end')
当数据接收完成时执行。
HTTPS 客户端
HTTPS 客户端与 HTTP 客户端非常相似,只是使用了 https
模块:
const https = require('https')
const options = {
hostname: 'example.com',
port: 443,
path: '/',
method: 'GET',
}
const req = https.request(options, (res) => {
let data = ''
// 接收数据
res.on('data', (chunk) => {
data += chunk
})
// 数据接收完成
res.on('end', () => {
console.log(data)
})
})
// 处理错误
req.on('error', (error) => {
console.error(`请求遇到问题: ${error.message}`)
})
req.end()
流程解析:
https.request
的用法和http.request
相同,唯一的区别是它通过 HTTPS 端口(默认是 443)连接,并使用加密通信。
4. 注意事项
HTTPS 的证书:在实际项目中,不建议使用自签名证书,因为自签名证书不受浏览器信任,用户访问时会看到安全警告。可以使用 Let's Encrypt 或其他认证机构颁发的证书。
端口号:HTTP 默认端口是 80,HTTPS 默认端口是 443。如果你想在这些端口运行服务器,可能需要管理员权限(尤其在 Linux 环境下)。
安全性:确保使用 HTTPS 保护敏感数据传输,例如用户登录、支付信息等。HTTP 是不加密的,数据可能会被第三方截获。
5. 结论
通过 http
和 https
模块,我们可以轻松创建 HTTP 和 HTTPS 服务器及客户端。HTTP 模块适用于不需要加密的数据传输,而 HTTPS 模块则适用于需要加密和安全性的场景。
八、进程与子进程
child_process
模块cluster
模块
进程和子进程是计算机程序中非常重要的概念。在 Node.js 中,我们使用 child_process
模块来创建和管理子进程,而 cluster
模块则用于将应用程序拆分为多个工作进程,以更好地利用多核 CPU。
1. 基础概念
进程(Process):进程是一个正在运行的程序实例,它有自己的内存空间和系统资源。Node.js 本身运行在一个单一的进程中。
子进程(Child Process):子进程是由一个父进程创建的进程,子进程可以执行不同的任务。
child_process
模块允许我们从 Node.js 应用中创建子进程,执行其他命令行指令或运行其他 Node.js 脚本。Cluster 模块:Node.js 是单线程的,但是
cluster
模块允许我们利用多核系统的优势,通过创建多个工作进程来处理并发请求,从而提高性能。
2. 子进程 (child_process
模块)
child_process
模块提供了几种创建子进程的方法:
exec()
:用于执行一个 shell 命令,结果通过回调函数返回。spawn()
:创建一个子进程并通过流与之交互。fork()
:专门用于派生新的 Node.js 进程,并通过 IPC(进程间通信)通道与父进程通信。
使用 exec()
执行命令
exec()
方法用于执行 shell 命令并在回调函数中获取输出:
const { exec } = require('child_process')
// 执行 ls 命令 (列出当前目录中的文件)
exec('ls', (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error.message}`)
return
}
if (stderr) {
console.error(`错误输出: ${stderr}`)
return
}
console.log(`命令输出: ${stdout}`)
})
流程解析:
exec()
执行给定的 shell 命令。stdout
是命令的标准输出,stderr
是标准错误。- 回调函数会在命令执行完成后调用,获取输出或错误信息。
注意事项:
exec()
适合用于执行轻量的命令,输出数据量较大时可能会遇到缓冲区溢出的问题。
使用 spawn()
创建子进程
spawn()
更适合处理长时间运行的进程,并且可以通过流与子进程进行实时交互。
const { spawn } = require('child_process')
// 使用 spawn 执行命令
const ls = spawn('ls', ['-lh', '/usr'])
// 监听标准输出
ls.stdout.on('data', (data) => {
console.log(`输出: ${data}`)
})
// 监听标准错误
ls.stderr.on('data', (data) => {
console.error(`错误: ${data}`)
})
// 监听子进程关闭
ls.on('close', (code) => {
console.log(`子进程退出,退出码: ${code}`)
})
流程解析:
spawn()
创建子进程,返回的对象可以监听数据流事件,如stdout
和stderr
。- 它允许在命令运行时实时处理输出数据。
注意事项:
spawn()
是基于流的,可以处理大量数据而不会像exec()
那样面临缓冲区溢出的问题。- 适用于需要与子进程进行实时交互的场景。
使用 fork()
创建 Node.js 子进程
fork()
是专门用于创建 Node.js 子进程的,它建立了一个独立的 Node.js 进程,并允许父进程与子进程通过 IPC 通信。
父进程代码:
const { fork } = require('child_process')
// 创建一个子进程,运行 child.js 文件
const child = fork('./child.js')
// 发送消息给子进程
child.send('你好,子进程')
// 监听子进程发来的消息
child.on('message', (msg) => {
console.log(`来自子进程的消息: ${msg}`)
})
子进程 (child.js
) 代码:
process.on('message', (msg) => {
console.log(`收到来自父进程的消息: ${msg}`)
// 向父进程发送消息
process.send('你好,父进程')
})
流程解析:
fork()
创建一个新的 Node.js 子进程,子进程执行一个独立的文件。- 父进程和子进程之间可以通过
send()
和message
事件进行通信。
注意事项:
fork()
非常适合在 Node.js 中执行多进程任务,特别是需要父子进程之间的通信时。- 每个子进程都是独立的,具有自己的内存空间。
3. Cluster 模块
Node.js 是单线程的,但在多核机器上,我们可以使用 cluster
模块来创建多个工作进程,每个工作进程都会监听同一个服务器端口,帮助处理更多并发请求。
基本用法
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
// 主进程:fork 工作进程
console.log(`主进程 ${process.pid} 正在运行`)
// Fork 一个新的子进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
// 如果工作进程退出,重新启动一个
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出`)
cluster.fork()
})
} else {
// 工作进程:创建 HTTP 服务器
http
.createServer((req, res) => {
res.writeHead(200)
res.end('Hello, Cluster!')
})
.listen(8000)
console.log(`工作进程 ${process.pid} 已启动`)
}
流程解析:
cluster.isMaster
判断当前是否是主进程,如果是主进程则创建子进程。cluster.fork()
创建工作进程,每个工作进程会运行同样的代码。- 每个工作进程会监听同一个端口,处理并发请求。
注意事项:
cluster
允许充分利用多核 CPU,显著提高性能。- 当工作进程崩溃时,主进程可以自动重启工作进程,确保服务的高可用性。
4. 结论
child_process
模块:允许我们创建子进程来执行系统命令、处理并发任务,或者与父进程进行通信。使用exec()
可以简单地执行 shell 命令,spawn()
适用于处理大量数据或长时间运行的任务,而fork()
则是专门为 Node.js 的多进程开发而设计的。cluster
模块:是一个重要的工具,帮助我们在多核机器上有效利用 CPU 资源,通过多进程来分担工作负载,从而提高应用程序的性能和并发处理能力。
通过这两个模块,我们可以充分利用系统资源,在复杂应用中实现更高效的并发和任务处理。
5. 应用场景举例
child_process
和 cluster
模块在实际开发中的应用场景各有侧重,它们分别用于提高 Node.js 应用的可扩展性、性能和并发处理能力。让我们通过具体的应用场景来理解它们的使用价值。
1. child_process
模块的应用场景
child_process
模块可以用于创建子进程并执行独立的任务,常见的应用场景包括:
(1) 并行任务执行
有时我们需要同时处理多个耗时任务,比如图像处理、数据转换或复杂计算。在 Node.js 的单线程环境下,这些任务会阻塞主事件循环,影响整体性能。使用 child_process.spawn()
或 fork()
可以创建多个子进程,独立处理任务而不阻塞主进程。
示例:图像处理应用 如果你的应用需要处理大量图像,可以使用子进程并行处理每张图像:
const { spawn } = require('child_process')
// 创建子进程,处理图像
const child = spawn('convert', [
'input.jpg',
'-resize',
'100x100',
'output.jpg',
])
child.on('close', (code) => {
console.log(`图像处理子进程结束,退出码:${code}`)
})
(2) 运行系统命令
如果你需要从 Node.js 中调用操作系统命令(例如备份数据库、执行脚本等),exec()
是最简单的解决方案。
示例:备份 MySQL 数据库
const { exec } = require('child_process')
exec(
'mysqldump -u user -p password database_name > backup.sql',
(error, stdout, stderr) => {
if (error) {
console.error(`备份失败: ${error.message}`)
return
}
console.log('数据库备份完成')
}
)
(3) 创建子进程运行独立的 Node.js 脚本
在大型项目中,某些模块或逻辑可能需要与主进程隔离运行,fork()
允许我们创建独立的 Node.js 子进程处理这些任务,同时还能通过 IPC 通信。
示例:多进程爬虫系统 你可以使用多个子进程并行抓取不同网页,提升爬虫的效率。
const { fork } = require('child_process')
// fork 创建多个爬虫子进程
for (let i = 0; i < 5; i++) {
const child = fork('./crawler.js', [i])
child.on('message', (data) => {
console.log(`爬虫进程 ${i} 返回的数据: ${data}`)
})
}
(4) 处理 CPU 密集型任务
Node.js 的单线程特点意味着,如果你有大量 CPU 密集型的任务(如计算密集型算法或处理大文件),它可能会阻塞主线程,影响其他请求的处理。通过 child_process
模块,你可以将这些任务移到子进程中进行处理,避免阻塞。
示例:计算密集型任务(如矩阵运算)
const { fork } = require('child_process')
// 将计算密集型任务交给子进程
const child = fork('./heavy-computation.js')
child.send({ matrix1, matrix2 })
child.on('message', (result) => {
console.log(`计算结果: ${result}`)
})
2. cluster
模块的应用场景
cluster
模块主要用于提高 Node.js 应用程序的性能,特别是当你需要处理大量并发请求时。Node.js 本质上是单线程的,但在多核 CPU 上,你可以通过 cluster
模块创建多个工作进程来充分利用硬件资源。
(1) 处理高并发请求
如果你的应用需要处理大量并发请求(例如 Web 服务、API 接口),单线程的 Node.js 会遇到性能瓶颈。通过 cluster
,你可以在多核机器上创建多个工作进程,让每个工作进程处理一部分请求,从而有效提升并发处理能力。
示例:高并发 Web 服务器
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
// 主进程:fork 工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出`)
cluster.fork() // 重新创建崩溃的进程
})
} else {
// 工作进程:处理 HTTP 请求
http
.createServer((req, res) => {
res.writeHead(200)
res.end('Hello Cluster!')
})
.listen(8000)
}
在多核机器上,每个 CPU 核心都可以运行一个工作进程,最大限度地利用硬件资源。这种方式特别适合像电商平台、社交网站等高并发场景。
(2) 避免单线程瓶颈
Node.js 的单线程适合 I/O 密集型任务,但对于 CPU 密集型任务可能会引起主线程的阻塞。通过 cluster
模块,多个工作进程可以并行处理 CPU 密集型任务,避免了阻塞。
示例:多进程计算 例如你需要处理大量数据分析任务,使用 cluster
可以让每个核心处理一部分数据,避免主线程被阻塞:
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
} else {
// 在这里进行复杂的计算任务
console.log(`工作进程 ${process.pid} 正在处理复杂任务`)
}
(3) 提高服务的可靠性
当工作进程崩溃时,cluster
主进程可以检测到,并自动重启新的工作进程,确保应用的高可用性。对于关键任务应用,特别是在生产环境中,确保高可用性至关重要。
示例:服务自动重启
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出`)
cluster.fork() // 自动重启
})
(4) 水平扩展
在分布式架构中,你可以通过 cluster
模块在单台机器上创建多个工作进程。结合负载均衡器,多个实例可以分摊负载,水平扩展能力非常强。
3. 总结
child_process
模块:适合处理独立、并行的任务,比如系统命令、外部工具的调用、并行计算等场景。尤其在需要运行长时间的任务或与外部资源交互时,它是非常强大的工具。cluster
模块:适合提高服务器的并发处理能力和可扩展性,尤其在多核机器上处理大量请求时,通过创建多个工作进程,充分利用 CPU 资源,提升性能并保障高可用性。
在实际应用中,你可以根据任务的复杂性和并发需求选择合适的方案来提高 Node.js 应用的效率。
九、Node.js 中的错误处理与断言处理
domain
模块EventEmitter
类
asseet
模块
在 Node.js 中,错误处理是确保应用稳定性和可靠性的重要组成部分。通过合理的错误处理机制,开发者可以捕获和处理异常,防止应用崩溃。Node.js 提供了几种处理错误的方式,其中包括 domain
模块 和 assert
模块。
1. 错误处理:domain
模块
domain
模块是在早期的 Node.js 中引入的,用于捕获异步代码中的未捕获的异常,帮助我们避免应用因为异常错误而崩溃。
注意
从 Node.js 4.x 开始,domain
模块被标记为废弃,不推荐在新的项目中使用。推荐的方式是使用现代的异步错误处理模式,例如 async/await
和全局异常处理器。
作用与概念
domain
模块:可以将多个异步操作分配到一个域中,所有这些操作发生的错误都会被该域捕获,从而避免未处理的错误导致整个程序崩溃。EventEmitter
类:EventEmitter
是 Node.js 中的核心类,处理事件驱动的编程。大多数 Node.js 对象都继承了EventEmitter
类,尤其是在处理异步操作时。
基本用法
- 创建域:通过
domain.create()
创建一个新的域。 - 绑定事件或代码块:可以将某个对象(如
EventEmitter
实例)或回调函数绑定到该域,这样其中发生的错误都可以被域捕获。 - 错误处理:域中的错误会被自动传递给
domain
的error
事件监听器。
示例代码
const domain = require('domain')
const EventEmitter = require('events')
// 创建一个新的域
const d = domain.create()
// 监听域中的错误
d.on('error', (err) => {
console.log(`域捕获到的错误: ${err.message}`)
})
// 在域中运行异步代码
d.run(() => {
const emitter = new EventEmitter()
// 将 EventEmitter 实例绑定到域
d.add(emitter)
// 模拟抛出一个错误
emitter.emit('error', new Error('发生错误'))
})
代码流程解析
domain.create()
创建了一个域对象d
。d.run()
在域中执行代码块,该代码块中的所有异步错误都被该域捕获。- 将
EventEmitter
对象与域关联后,emitter.emit('error')
抛出的错误会被d
捕获,而不会导致程序崩溃。
注意事项
- 弃用:
domain
模块已经被弃用,不推荐在新项目中使用。在现代 Node.js 中,应使用更好的错误处理方式(如 Promise、async/await 和全局错误处理)。 - 全局域:尽量避免使用全局域,因为这可能会捕获过多的错误,导致调试变得困难。
应用场景
- 老旧代码中的错误处理:在早期版本的 Node.js 项目中,
domain
模块被广泛用于捕获异步操作中的错误,特别是在无法使用 Promise 或 async/await 时。 - 复杂异步场景:在一些复杂的异步场景中,可以用
domain
模块将多个相关的异步操作捕获到同一个错误处理器中。
2. 断言处理:assert
模块
assert
模块是 Node.js 的内置模块,用于测试和验证代码中是否满足某些条件。它主要用于调试和单元测试,以确保程序按预期运行。如果断言失败,assert
会抛出一个错误。
作用与概念
assert
模块:提供了简单的断言方法,帮助开发者验证程序逻辑。在开发和测试阶段,使用断言可以确保代码的正确性。如果某个条件不满足,它会抛出错误提示开发者。- 断言的原理:断言是一种主动的检查机制,用于验证某个条件是否为真。如果条件为假,则说明代码存在问题,立即抛出错误,避免代码进入不正确的状态。
基本用法
assert
提供了多种常用的断言方法,如 assert.ok()
、assert.strictEqual()
、assert.deepStrictEqual()
等。
示例代码
- 基本断言
const assert = require('assert')
// 断言成功
assert.ok(true) // 不会抛出错误
// 断言失败
assert.ok(false, '该条件不为真') // 抛出错误: 该条件不为真
- 严格相等断言
const assert = require('assert')
// 断言两个值严格相等
assert.strictEqual(5, 5) // 不会抛出错误
// 断言失败
assert.strictEqual(5, '5', '这两个值不严格相等') // 抛出错误: 这两个值不严格相等
- 深度比较对象
const assert = require('assert')
// 深度比较两个对象
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 1, b: 2 }
assert.deepStrictEqual(obj1, obj2) // 不会抛出错误
const obj3 = { a: 1, b: 3 }
assert.deepStrictEqual(obj1, obj3) // 抛出错误: 对象不相等
代码流程解析
assert.ok()
检查给定的条件是否为真。如果条件为假,它会抛出错误。assert.strictEqual()
用于检查两个值是否严格相等(===
)。如果不相等,抛出错误。assert.deepStrictEqual()
用于深度比较两个对象或数组。如果它们的结构或内容不同,则抛出错误。
注意事项
- 仅在开发和测试中使用:
assert
模块主要用于测试和调试阶段,在生产环境中应避免使用它,因为断言失败会抛出错误,导致程序终止。 - 严格比较:
strictEqual
和deepStrictEqual
使用的是严格相等 (===
) 和深度比较,这意味着它们在比较对象时会更加严格。 - 有条件的测试:如果你希望某个条件的结果不影响生产环境的运行,只用于验证逻辑,建议在测试环境中使用断言。
应用场景
- 单元测试:
assert
模块广泛用于编写单元测试,帮助开发者验证程序的正确性。它提供了简单、直接的 API 来检查函数输出和行为是否符合预期。 - 调试:在开发过程中,断言可以用来快速定位问题,通过在关键的逻辑点插入断言,可以检测程序是否按照预期运行,避免出现逻辑错误。
- 验证输入参数:在函数或模块中,可以使用断言来验证传入的参数是否符合预期格式或类型,以确保程序的健壮性。
3. 总结
domain
模块:用于捕获异步操作中的未处理错误,帮助管理错误流,并防止程序崩溃。然而,由于它的复杂性和局限性,已被标记为弃用。现代 Node.js 开发通常使用async/await
和全局错误处理机制替代它。适用于旧的异步场景和早期的 Node.js 项目。assert
模块:用于进行代码中的逻辑断言和验证,主要应用于单元测试和调试场景。通过assert
,开发者可以确保某些条件在开发过程中始终得到满足,如果条件不满足,程序会立即抛出错误并终止。
通过这两个模块,我们可以更好地捕获错误、调试代码并确保逻辑正确性。在开发和测试阶段,充分利用断言和错误处理机制可以提高代码的健壮性,减少潜在问题。
十、加密与压缩
crypto
模块zlib
模块
在 Node.js 中,加密和压缩是两个非常重要的操作,广泛应用于数据传输、存储和安全性。Node.js 提供了专用的模块来处理这些任务,分别是 crypto
模块(用于加密、解密和哈希计算)和 zlib
模块(用于数据压缩和解压缩)。
1. 加密:crypto
模块
crypto
模块是 Node.js 中用于处理加密和解密操作的核心模块。它支持多种加密算法(如 AES、RSA)以及常见的哈希算法(如 MD5、SHA-256)。常见的用途包括数据加密、解密、生成数字签名、哈希计算等。
作用与概念
- 加密/解密:通过对称(如 AES)或非对称(如 RSA)加密算法对数据进行加密处理,使数据在传输或存储时不可被未授权者轻易读取。
- 哈希算法:哈希算法是一种不可逆的加密算法,主要用于数据校验和生成唯一标识(如文件的哈希值)。常用的哈希算法包括 MD5、SHA-256。
- 随机数生成:
crypto
模块还提供安全的随机数生成方法,广泛用于加密密钥和安全令牌的生成。
基本用法
- 哈希计算
哈希算法常用于生成唯一的消息摘要。比如,文件哈希可用于验证文件的完整性。
const crypto = require('crypto')
// 使用 SHA-256 生成哈希
const hash = crypto.createHash('sha256')
// 更新要进行哈希的数据
hash.update('Hello, world!')
// 计算并输出哈希值(以十六进制字符串形式)
console.log(hash.digest('hex')) // 输出: <哈希值>
- 对称加密/解密
对称加密是一种使用相同的密钥进行加密和解密的算法。常用的算法如 AES。
const crypto = require('crypto')
// 密钥和加密算法
const algorithm = 'aes-256-cbc'
const key = crypto.randomBytes(32) // 32字节密钥
const iv = crypto.randomBytes(16) // 初始化向量 (IV)
// 加密函数
function encrypt(text) {
const cipher = crypto.createCipheriv(algorithm, key, iv)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
return encrypted
}
// 解密函数
function decrypt(encryptedText) {
const decipher = crypto.createDecipheriv(algorithm, key, iv)
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
// 示例加密与解密
const text = 'Hello, Node.js!'
const encrypted = encrypt(text)
console.log('加密后的文本:', encrypted)
const decrypted = decrypt(encrypted)
console.log('解密后的文本:', decrypted)
- 生成随机数
生成安全的随机密钥或令牌可以用于会话管理、OAuth 或身份验证系统中。
const crypto = require('crypto')
// 生成 16 字节的随机数作为密钥
const token = crypto.randomBytes(16).toString('hex')
console.log('生成的随机令牌:', token)
注意事项
- 安全性:在加密过程中,密钥管理至关重要。应尽量避免将密钥硬编码在代码中,推荐使用环境变量或安全存储服务。
- 性能开销:复杂的加密算法(如 RSA、AES-256)可能会导致性能瓶颈,尤其是在大量加密/解密操作时。哈希算法相比加密算法通常更高效。
- 哈希不可逆:哈希算法是单向的,无法反向解密,所以它主要用于验证而非加密。
应用场景
- 数据加密和解密:在传输敏感信息(如密码、财务信息)时,使用加密算法保障数据安全性。
- 文件完整性校验:通过计算文件的哈希值,确保文件在传输过程中未被篡改。
- 数字签名与验证:在身份验证或签名过程中使用非对称加密(如 RSA)保证数据的完整性和身份的真实性。
2. 压缩:zlib
模块
zlib
模块提供了压缩和解压缩功能,主要基于 gzip
、deflate
和 brotli
等算法。压缩能够减少数据的体积,从而提高传输效率和减少存储空间。
作用与概念
- 数据压缩:通过压缩算法减少文件或数据的大小,使其在网络传输或存储时更加高效。
- 数据解压缩:与压缩相反,解压缩用于恢复原始数据。
- 流式压缩:
zlib
支持流式压缩和解压缩,这对大文件处理特别有用,可以分块处理数据,减少内存占用。
基本用法
- 压缩文件/数据
const zlib = require('zlib')
const fs = require('fs')
// 创建读取和写入流
const input = fs.createReadStream('input.txt')
const output = fs.createWriteStream('input.txt.gz')
// 使用 zlib 压缩文件
input.pipe(zlib.createGzip()).pipe(output)
console.log('文件已压缩')
- 解压缩文件
const zlib = require('zlib')
const fs = require('fs')
// 创建读取和写入流
const input = fs.createReadStream('input.txt.gz')
const output = fs.createWriteStream('input_uncompressed.txt')
// 使用 zlib 解压文件
input.pipe(zlib.createGunzip()).pipe(output)
console.log('文件已解压')
- 流式压缩和解压缩
流式操作对处理大数据文件非常有效。比如在处理大型日志文件时,能够一边读取文件,一边压缩或解压缩。
const zlib = require('zlib')
const { pipeline } = require('stream')
const fs = require('fs')
// 流式压缩
const gzip = zlib.createGzip()
const source = fs.createReadStream('largefile.txt')
const destination = fs.createWriteStream('largefile.txt.gz')
// 使用 pipeline 管道流处理
pipeline(source, gzip, destination, (err) => {
if (err) {
console.error('压缩失败:', err)
} else {
console.log('文件已成功压缩')
}
})
注意事项
- 选择合适的算法:
zlib
提供了多种压缩算法,如gzip
、deflate
和brotli
,不同的算法有不同的压缩率和解压缩速度,选择时应根据场景权衡压缩效率和性能开销。 - 压缩与解压的对称性:必须确保使用相同的压缩算法来解压缩数据。例如,用
gzip
压缩的数据必须使用gunzip
解压。 - 内存消耗:压缩和解压缩需要处理大量数据,尤其是流式压缩处理大文件时,使用流的方式可以避免一次性加载过多数据导致的内存溢出。
应用场景
- 网络传输:在 Web 开发中,使用
gzip
或brotli
压缩可以减少静态资源(如 HTML、CSS、JavaScript 文件)的大小,提升网页加载速度。 - 日志压缩:在日志管理系统中,日志文件往往非常大,通过压缩保存可以大大减少存储空间。
- 文件存储:压缩图片、文本文件等可以有效节省磁盘空间,特别是在备份系统中,压缩文件以减少存储成本。
3. 总结
crypto
模块:用于加密、解密、生成哈希值和随机数,保证数据的安全性和完整性。它在需要保护敏感信息和数据校验的场景下尤为重要。zlib
模块:用于数据的压缩和解压缩。它能够减少文件体积,提高网络传输效率,常用于网络数据压缩、文件压缩和解压。
加密和压缩是数据安全和高效传输的关键技术。在实际开发中,可以根据不同的需求和场景,灵活选择适当的加密算法与压缩方式。