Skip to content

Nodejs 原理

Basic 知识

Buffer

  • Buffer 继承自且约等于 Unit8Array
  • 8位无符号整型数组,每个元素为 0 ~ 255
  • 创建:Buffer.from()

Buffer.from([1, 1, 1, 1]) 与 Buffer.from([257, 257.5, -255, '1']) 取值相同

  • Buffer 是 Node.JS 中用于操作 ArrayBuffer 的视图,是 TypedArray 的一种。

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是一个字节数组,可读但不可直接写。

也可利用 DataView 来操作 ArrayBuffer 的数据。

EventEmitter

  • 事件队列机制
  • const EventEmitter = require('events');

Stream

  • Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。如 http 请求,进程日志输出(stdout),文件读写。
  • 所有的 Stream 对象都是 EventEmitter 的实例。
  • Stream 内部含有 Buffer。
  • 当 Stream 内部有数据可读是,emit data 事件。
  • Stream 是可写的,通过调用 write()/end() 写数据到 Buffer。
  • Stream 类型

    • 可读 Readble
    • 可写 Writable
    • 双工 Duplex
    • 转换 Transform

    读出内容,可以进行压缩等转换。

  • Stream 对象事件

    • data - 当有数据可读时触发。
    • end - 没有更多的数据可读时触发。
    • error - 在接收和写入过程中发生错误时触发。
    • finish - 所有数据已被写入到底层系统时触发。

Socket

  • 套接字,一个对 TCP / IP协议进行封装的 编程调用接口(API)
Socket = { (IP地址1:PORT端口号),(IP地址2:PORT端口号) }
  • 可以基于TCP或UDP协议。
  • TCP

    • 三次握手建立连接

    第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认。

    第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认。

    第三次握手:客户端收到服务器的(SYN+ACK)报文段,并向服务器发送ACK报文段。

    x

    • 四次挥手关闭连接

    假设A主动释放连接:

    第一次挥手:A发送释放信息到B;(发出去之后,A->B发送数据这条路径就断了)。

    第二次挥手:B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求。

    第三次挥手:B发送“请求释放连接“信息给A。

    第四次挥手:A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求。

    x

  • Code

/* 一个利用 socket 实现的 server */
const net = require('net');
const fs = require('fs');
const path = require('path');

const server = net.createServer(socket => {
  socket.on('data', request => {
    const req = request.toString('utf-8');
    console.log(req);

    const lines = req.split('\r\n');
    const [method, url, protocol] = lines[0].split(' ');

     switch(url) {
      case '/':
        responseHTML(socket, path.resolve('./index.html'));
        break;
      default:
        responseHTML(socket, path.resolve('./404.html'));
        break;
     }    
  });

  socket.on('error', e => {
    console.log(e);
  });
});

const PORT = 8001;
server.listen(PORT, () => {
  console.log(`Socket is listening on ${PORT}!`);
});

function responseHTML(socket, htmlPath) {
  const content = fs.readFileSync(htmlPath);

  socket.write(
    'HTTP/1.1 200 OK\r\n' +
    'Content-Type: text/html\r\n' +
    'Content-Length: ' + content.length + '\r\n' +
    '\r\n' +
    content
  );
}

Websocket

  • 在单个 TCP 连接上进行全双工通讯的 协议
  • Websocket 并非基于 HTTP 协议,只是在 Upgade 切换协议阶段利用了 HTTP。
  • Server Code
const http = require('http');

const server = http.createServer((req, res) => {});

server.on('upgrade', (req, socket, head) => {
  console.log('客户端说要升级到 Websocket 协议');

  socket.write(
    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
    'Upgrade: WebSocket\r\n' +
    'Connection: Upgrade\r\n' +
    '\r\n'
  );

  socket.pipe(socket); // echo back

  socket.on('data', request => {
    console.log('from client', request.toString('utf-8'));
  });

  // server -> client
  socket.write('100');
  console.log('socket send 100');

  // later: server -> client
  setTimeout(() => {
    socket.write('200');
    console.log('socket send 200');
  }, 2000);

  // close socket after 4s
  setTimeout(() => {
    socket.destroy();
    console.log('socket destroy');
  }, 4000);
});

const PORT  1337;

server.listen(PORT, () => {
  console.log(`ws-server is listening on ${PORT}!`);
});
  • Client Code
const http = require('http');

const options = {
  port: 1337,
  host: '127.0.0.1',
  headers: {
    'Connection': 'Upgrade',
    'Upgrade': 'Websocket'
  }
};

// make a request
const req = http.request(options);
req.end();

req.on('upgrade', (req, socket, upgradeHead) => {
  console.log('Websocket upgade 完成');

  socket.on('data', request => {
    console.log('from server', request.toString('utf-8'));
  });
});

Nodejs 服务部署

  • 使用 pm2 启动服务端、进行运维
  • pm2 start server.js --name my-server
  • pm2 list
  • pm2 monit
  • pm2 logs server.js

底层依赖

v8 引擎

javascript 语法解析引擎。

libuv.c

C 语言实现的一个高性能异步阻塞 IO 库,实现 Nodejs 的 事件循环。 跨平台的异步 IO 处理模型。

  • C 语言实现的一个高性能异步阻塞 IO 库,实现 Nodejs 的 事件循环。
  • 跨平台的异步 IO 处理模型。
  • 不同平台的胶水层

window IOCP linux epoll / kqueue

http-parse/http

底层处理 http 请求,处理报文,解析请求内容等。

openssl

处理加密算法,社区中很经典的加密算法库,很多框架都用到。

zlib

处理压缩算法相关的内容。

常见内置模块

fs

文件系统,能读写当前系统中硬盘数据。

path

路径系统,处理路径之间的问题。

crypto

加密模块,以标准的加密方式对内容进行加解密。

dns

处理 dns 相关问题,比如设置 dns 服务器。

http

设置 http 服务器,发送请求,监听响应。

readline

读取 stdin 的一行内容,可以读取,增加,删除命令行中的内容。

os

操作系统层面的 API。

vm

一个专门处理沙箱的虚拟机模块,底层 调用 v8 的 API 来进行代码解析

module

CommonJS 模块化规范的实现原理

Nodejs 周边

Quickjs

一个 js 解析引擎,类似 V8 引擎,但及其轻量。

  • 轻量而易于嵌入
  • 快速启动,快速解释。运行完整实例的生命周期不到 300 微秒。
  • 完整的实现对 ES2019 的支持。
  • 可将 javascript 编译成没有外部依赖的可执行文件。

Deno

  • 相同于 Nodejs

    • 底层基于 v8,上层封装以下系统级调用
    • 可使用 javascript 开发
  • 不同于 Nodejs

    • 基于 rust 和 Typescript 开发一些上层模块,所以天生支持 ts。
    • 支持 url 加载模块,同时支持 top-level await 等特性。

实战

login/cookieparser

  • Code
const cookieParser = require('cookie-parser');

app.use(cookieParser);

app.post('api/login', async function(req, res) {
  const { username, password } = req.body;
  const user  = await User.findOne({
      where: {  username, password }
  });

  res.cookie('loginToken', user.userId, {
      maxAge: 15 * 60 * 1000,
      httpOnly: true
  });

   res.json({ data: 'ok' });
});
  • 说明

响应头 Set-Cookie 字段;

之后的请求 Request Headers 里会带上 Cookie 字段

Debug

  • 命令
node --debug-brk --inspect debug.js

参考