当前位置: 首页 > news >正文

chapter3 node.js基础服务器搭建与机制剖析

在传统的动态编程语言(如php)中,需要依赖外部的服务器来进行请求的接收,但在node.js中,则无需担心这个问题,node.js本身内置了一个http服务器,开发者可以直接使用node的模块来进行服务器的创建而无需依赖外部的服务器。

初识node应用

在之前的学习中,我们通过node运行了一个js程序,这实际上只是一个脚本,而非规范的node应用,实际上,一个规范的node应用包含以下几部分:

  • require 指令:在 Node.js 中,使用 require 指令来加载和引入模块,引入的模块可以是内置模块,也可以是第三方模块或自定义模块。
  • **创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
  • 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

require 指令

require指令用于加载不同的模块进行开发,其基础语法为:

const http=require('http')//加载http模块并赋给一个http对象

这样会将一个模块进行加载并赋给http变量

基础服务器的创建

在加载完http模块后,我们调用其createServer方法创建一个基础的服务器并监听8888端口:

var http = require('http');
//创建服务器,监听8088端口
http.createServer(function (request, response) {
//请求时的回调// 发送 HTTP 头部 // HTTP 状态值: 200 : OK// 内容类型: text/plainresponse.writeHead(200, {'Content-Type': 'text/plain'});// 发送响应数据 "Hello node"response.end('Hello node!!\n');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');

访问地址后,应该可以看到一个hello node的输出。
下面,我们来了解一些node.js相关的工作机制与相关架构。

Node.js 的工作机制

核心特点

node.js工作机制的核心特点是:单线程、事件循环、非阻塞I/O、跨平台

核心架构组成

Node.js 通过 V8 引擎执行 JavaScript 代码,使用 Node.js API 与操作系统交互,并通过 Libuv 处理异步 I/O 操作。事件循环和工作线程确保了 Node.js 的高效和非阻塞特性。

  • V8 JavaScript Engine:这是 Node.js 的核心,负责执行 JavaScript 代码。V8 是 Chrome 浏览器的 JavaScript 引擎,它将 JavaScript 代码编译成机器码以提高执行效率。
  • Node.js Bindings (Node API):这一层提供了一组 API,允许 JavaScript 代码与操作系统进行交互。这些 API 包括文件系统、网络、进程等操作。
  • Libuv (Asynchronous I/O):Libuv 是一个跨平台的异步 I/O 库,它在 Node.js 下运行,用于处理文件系统、网络和进程等异步操作。Libuv 使用事件循环和工作线程来处理这些操作,而不会阻塞主线程。
  • Event Loop:这是 Node.js 的核心概念之一。事件循环不断检查事件队列,处理事件和执行回调函数。它确保了 Node.js 的非阻塞和事件驱动的特性。
  • Event Queue:事件队列用于存储即将处理的事件。当一个异步操作完成时,相关的回调函数会被放入事件队列中,等待事件循环处理
  • Worker Threads:这些是用于处理阻塞操作的线程,如文件读写、网络请求等。它们允许 Node.js 在不阻塞主线程的情况下执行这些操作。
  • Blocking Operation:这些是可能阻塞线程的操作,如同步的文件读写。在 Node.js 中,这些操作通常被放在工作线程中执行,以避免阻塞事件循环。
  • Execute Callback:一旦一个异步操作完成,它的回调函数就会被执行。这是通过事件循环来管理的。
    简而言之,Node.js通过V8引擎来执行Js代码,Libuv 管理异步 I/O,libuv中又通过事件循环与工作线程实现高性能、非阻塞的事件驱动架构。

node.js架构分层

node.js可以分为javascript层、C++绑定层、依赖层:

javascript层

面向开发者的层面,包括:

  • 核心模块:目录操作fs、路径操作path、网络通信http等等
  • npm提供的第三方模块
  • 开发者本地开发的模块

c++绑定层

这一层将底层功能暴露给 JavaScript 层,包括:

  • node API:- Node.js 核心 API 的 C++ 实现
  • V8接口的封装

依赖层

这一层是最底层的依赖:

  • V8引擎:用于解释运行javascript的引擎
  • libuv:异步I/O库
  • c-ares:异步 DNS 解析库
  • zlib:压缩功能支持
  • openssl:加密支持

事件循环

node.js一个比较核心的工作机制就是它的事件循环,它带来了良好的异步操作调度,它有以下几个阶段:

  1. timers 阶段:处理setimtout与setinterval定时器的回调,到达预设时间后,相关回调会进入该阶段执行。
  2. pending callbacks 阶段:执行某些系统操作(如 TCP 错误类型)的回调。
  3. idle, prepare 阶段:Node.js 内部使用,供系统做一些准备工作
  4. poll 阶段:执行I/O相关操作的回调,如果 poll 队列不为空,则同步地执行回调,直到队列为空或达到系统限制,队列为空时会根据定时器到期与其他情况进行阶段的切换。
  5. check 阶段:执行 setImmediate() 注册的回调。
  6. close callbacks 阶段:执行事件关闭的回调

非阻塞 I/O 原理

node.js的I/O效率非常高,这是因为采用了非阻塞I/O的设计,它的流程如下:

  1. 应用发起I/O的请求
  2. node.js将请求给底层的libuv进行处理
  3. libuv使用系统提供的异步接口进行处理
  4. 主线程继续执行其他的任务
  5. I/O 完成后,回调函数被放入事件队列
  6. 事件循环在适当阶段执行回调

工作机制带来的缺点

node.js的这一套工作机制与I/O模型提供了极高的高并发能力,但也带来了一些缺点:

  1. 单线程限制:所有 JavaScript 代码在主线程上运行,一旦出现cpu密集型计算任务(机器学习,大规模计算),事件循环会被阻塞,影响其他请求的处理。
  2. 异步编程导致回调嵌套:由于采用异步编程,在回调时可能会出现回调地狱,影响维护与错误排查。
  3. 多核利用率不足:node.js默认是单线程,无法自动利用多核 CPU,需要通过 clusterworker_threads 手动实现多进程/线程调度,增加开发复杂度。

单线程限制的优化

在上面我们提到了node.js是单线程的,这意味这它无法良好的利用多核性能,但也提供了一些方法来利用多核cpu:

worker threads(推荐)

node.js从v10.5起引入的特性,真正的创建多个线程来并行处理任务:

// main.js
const { Worker } = require('worker_threads');const worker = new Worker('./worker.js', {workerData: { number: 42 }
});worker.on('message', msg => console.log('来自子线程的结果:', msg));
worker.on('error', err => console.error('子线程报错:', err));
worker.on('exit', code => console.log('子线程退出,code:', code));
// worker.js
const { parentPort, workerData } = require('worker_threads');// 假设是个耗时计算
let result = workerData.number * 2;parentPort.postMessage(result);

Child Process(创建子进程)

使用 child_process 模块可以创建新的 Node.js 进程(不是线程),适合进行 多进程并行任务:

// main.js
const { fork } = require('child_process');const child = fork('child.js');child.send({ number: 100 });child.on('message', (msg) => {console.log('主进程收到:', msg);
});
// child.js
process.on('message', (data) => {let result = data.number + 1;process.send({ result });
});

Cluster 模块(主进程 + 多个工作进程)

利用 cluster 模块可以创建多个 Node.js 进程,共享服务器端口,实现类似多线程的服务负载:

// cluster_server.js
const cluster = require('cluster');
const http = require('http');
const os = require('os');if (cluster.isPrimary) {const numCPUs = os.cpus().length;console.log(`主进程 ${process.pid} 正在运行`);// 创建多个工作进程for (let i = 0; i < numCPUs; i++) {cluster.fork();}cluster.on('exit', (worker) => {console.log(`工作进程 ${worker.process.pid} 已退出`);});
} else {http.createServer((req, res) => {res.end(`来自工作进程 ${process.pid}`);}).listen(3000);console.log(`工作进程 ${process.pid} 启动`);
}
// cluster_server.js
const cluster = require('cluster');
const http = require('http');
const os = require('os');if (cluster.isPrimary) {const numCPUs = os.cpus().length;console.log(`主进程 ${process.pid} 正在运行`);// 创建多个工作进程for (let i = 0; i < numCPUs; i++) {cluster.fork();}cluster.on('exit', (worker) => {console.log(`工作进程 ${worker.process.pid} 已退出`);});
} else {http.createServer((req, res) => {res.end(`来自工作进程 ${process.pid}`);}).listen(3000);console.log(`工作进程 ${process.pid} 启动`);
}
http://www.sczhlp.com/news/2028/

相关文章:

  • Day30
  • ElasticSearch-单节点安装
  • 20250730 - AnyswapV4Router 授权漏洞: 绕过了不存在的 permit 函数
  • WGCNA
  • 7-30-复习
  • 在 mac 系统上制作 Ubuntu Server 的 U 盘启动镜像
  • 预测LLM微调与遗忘副作用的新方法MNEME
  • 搞前端还有出路吗?如果有,在哪里?
  • git 数据结构探究之blob
  • [Unity] 人物悬挂与攀爬的实现思路
  • 关于《大道至简》的读后感
  • P1258 小车问题
  • 一文掌握最新版本Monocle3单细胞轨迹(拟时序)分析
  • 你改悔罢
  • Golang基础笔记十六之反射
  • 2.8 rt-thread spi flash挂载w25q128 liffes补充
  • 7 月 30 日模拟赛总结 - sb
  • 7-30破防
  • [JOI 2023 Final] 迷宫 / Maze
  • JPEG图像原理与应用|库的移植
  • 软工作业day29
  • DeepChat使用MCP-Hub 案例六 (结合FastMcp案例四)
  • webapi第五天
  • 完整教程:WPF的一些基础知识学习记录
  • 普通用户修改repo文件下载rpm包
  • MX galaxy Day14
  • 【Could not find Chrome This can occur if either】
  • ModelGate 支持 Claude Code,一键设置AI编程助手,开发效率极速提升!
  • linux storage stack 学习
  • JAVA语言学习总结(第29天)