从 HTTP 到 WebSocket:深入 Vite HMR 的网络层原理

Vite 的热模块替换(HMR,Hot Module Replacement)能够在修改代码后,仅更新受影响的模块,而无需刷新整个页面,从而保留应用状态。

Vite HMR 是一个典型的服务端-客户端协作系统

  • 服务端:监听文件变化,重新编译模块,通过 WebSocket 推送更新消息。
  • 客户端:接收消息,动态请求新模块,执行 HMR 回调,完成模块替换。

整个过程基于 原生 ES 模块,无需打包,按需编译,因此速度极快。

HTTP

node关于网络编程有 net、dgram、http、https

根据 TCP/IP 模型,层次关系如下:

层次 协议/模块 说明
应用层 http / https 定义应用程序如何交换数据(如 HTTP 请求/响应)。
表示层(TLS) tls 提供加密、身份验证,保障数据传输安全。
传输层 net(TCP) 提供可靠的、面向连接的字节流传输。
网络层 IP 路由和寻址(Node.js 中不直接暴露)。

创建 TCP 服务器

在 Node.js 中,使用内置的 net 模块可以轻松构建 TCP 服务器。TCP 是面向连接的、可靠的传输层协议,适合自定义协议通信、实时数据传输等场景。

js 复制代码
const net = require('net');

// 创建 TCP 服务器
const server = net.createServer((socket) => {
  console.log('客户端已连接,地址:', socket.remoteAddress, '端口:', socket.remotePort);

  // 接收客户端数据
  socket.on('data', (data) => {
    const message = data.toString().trim();
    console.log('收到消息:', message);

    // 回显消息(可选)
    socket.write(`服务端收到:${message}\n`);
  });

  // 连接关闭
  socket.on('end', () => {
    console.log('客户端已断开');
  });

  // 错误处理
  socket.on('error', (err) => {
    console.error('Socket 错误:', err.message);
  });
});

// 监听端口
const PORT = 8080;
server.listen(PORT, () => {
  console.log(`TCP 服务器运行在端口 ${PORT}`);
});
js 复制代码
net.createServer([options][, connectionListener])

connectionListener 是连接事件 connection 的侦听器。也可以采用下面这种方式进行侦听。

js 复制代码
const net = require('net');

const server = net.createServer();
server.on('connection', (socket) => {
  console.log('客户端已连接,地址:', socket.remoteAddress, '端口:', socket.remotePort);

  // 接收客户端数据
  socket.on('data', (data) => {
    const message = data.toString().trim();
    console.log('收到消息:', message);

    // 回显消息(可选)
    socket.write(`服务端收到:${message}\n`);
  });

  // 连接关闭
  socket.on('end', () => {
    console.log('客户端已断开');
  });

  // 错误处理
  socket.on('error', (err) => {
    console.error('Socket 错误:', err.message);
  });
});

// 监听端口
const PORT = 8080;
server.listen(PORT, () => {
  console.log(`TCP 服务器运行在端口 ${PORT}`);
});

客户端

js 复制代码
const net = require('net');

const client = net.createConnection({ port: 8080 }, () => {
  console.log('已连接到服务器');
  client.write('Hello, Server!');
});

client.on('data', (data) => {
  console.log('服务端回复:', data.toString());
  client.end(); // 发送完一次后关闭连接
});

client.on('error', (err) => {
  console.error('客户端错误:', err.message);
});

client.on('close', () => {
  console.log('连接已关闭');
});

WebSocket

在 WebSocket 出现之前,实现实时通信主要依赖以下技术:

  • HTTP 轮询:客户端定时向服务器发送请求,检查是否有新数据。缺点:大量无效请求,延迟高。
  • 长轮询:客户端发起请求,服务器保持连接直到有数据才返回,然后客户端立即发起下一次请求。虽然减少了请求次数,但仍有 HTTP 头部开销,且连接维持成本高。
  • Server-Sent Events (SSE) :服务器单向推送,但无法双向通信。

WebSocket 通过一次 HTTP 握手,将连接升级为全双工、低延迟的持久连接,从根本上解决了实时通信的痛点。

浏览器 Websocket(原生API)

html 复制代码
  <script>
    const socket = new WebSocket("ws://localhost:5177");
    socket.onopen = (event) => {
      console.log("-----连接成功-----", event);
    };
    socket.addEventListener("message", (event) => {
      console.log("收到消息", event);

      socket.send("client 已接收到消息!");
    });
    socket.addEventListener("error", (error) => {
      console.log("错误", error);
    });
    socket.addEventListener("close", (event) => {
      console.log("关闭", event);
    });
   </script>

当浏览器通过 WebSocket 与服务端通信时,Network 面板中不会为每一条消息单独显示新的请求 。WebSocket 连接一旦建立(握手请求会显示在 Network 的 WS 标签中),后续的消息传输都以帧(frame) 的形式在同一个持久连接上进行,不会产生新的 HTTP 请求记录。

浏览器发送 WebSocket 请求的唯一时机是 JavaScript 代码显式执行 new WebSocket(url)。除此之外,浏览器不会自动发起 WebSocket 连接。

因此,接收服务端消息时,Network 中不会新增 ws 请求,但会在已建立连接的 Messages 面板中显示消息内容。这是 WebSocket 协议的特性:它是一个长连接,而非每个消息独立请求。

支持发送哪些数据类型?

浏览器 WebSocket 的 send 方法严格按照 WebSocket API 规范,只接受以下数据类型:

  • DOMString(字符串)
  • Blob
  • ArrayBuffer
  • ArrayBufferView (如 Uint8Array

如果传入一个普通 JavaScript 对象(如 { foo: 'bar' }),浏览器并不会报错,而是会自动调用对象的 toString 方法 ,将其转换为字符串 "[object Object]" 后发送。因此,看到"能发送对象"的假象,实际发送的是无意义的字符串,而不是期望的 JSON 结构。

Node.js WebSocket 服务端(基于ws库)

js 复制代码
const WebSocket = require("ws");

const wss = new WebSocket.Server({
  port: 5177,
  perMessageDeflate: true,
});

wss.on("connection", (ws, req) => {
  // 1、验证来源 origin

  // 2、验证协议
  // 发送欢迎消息
  ws.send(JSON.stringify({ type: "welcome", message: "连接成功!" }));

  // 监听客户端消息
  ws.on("message", (data) => {
    console.log("来自client的消息", data.toString());
  });

  ws.on("close", () => {
    console.log("关闭");
  });

  sendMessageToAll(ws);

  console.log("connection success");

  ws.on("error", (error) => {
    console.log("错误", error);
  });
});

const totle = 10;
let num = 0;
let intervalId;
function sendMessageToAll(ws) {
  intervalId = setInterval(() => {
    num++;
    if (num >= totle) {
      clearInterval(intervalId);
      intervalId = null;
      return;
    }
    ws.send(
      JSON.stringify({
        type: "time",
        message: "num" + num + "当前时间:" + new Date().toLocaleString(),
      }),
    );
  }, 1000);
}

创建 WebSocket 服务器时,只能从三种模式中选择一种:

  1. 监听指定端口。
  2. 附着到已有 HTTP/HTTPS 服务器。
  3. 不监听任何连接(手动处理升级)
js 复制代码
// 模式1:监听指定端口
const wss = new WebSocket.Server({ port: 8080 });

// 模式2: 附着到已有 HTTP 服务器
const server = http.createServer();
const wss = new WebSocket.Server({ server });

// 模式3: 监听连接(手动处理升级)
const wss = new WebSocket.Server({ noServer: true });
// 然后自己监听 server 的 'upgrade' 事件,调用 wss.handleUpgrade

Websocket 协议 与 传输

WebSocket 握手

客户端与服务器建立 WebSocket 连接时,会先发起一个标准的 HTTP 请求,这个请求包含一个Upgrade头,表明客户端希望从 HTTP 协议升级到 WebSocket 协议。服务器如果支持 WebSocket,会以 101 状态码响应,完成握手过程。

js 复制代码
// 构建响应头:创建 101 状态码的响应头,指示协议切换
const headers = [
  'HTTP/1.1 101 Switching Protocols',
  'Upgrade: websocket',
  'Connection: Upgrade',
  `Sec-WebSocket-Accept: ${digest}`
];

WebSocket 关闭握手过程

  1. 任意一方发起关闭

    • 端点(客户端或服务器)发送一个 Close 控制帧 (opcode = 0x08)。
    • 该帧可以包含一个 状态码 (如 1000 表示正常关闭)和可选的 关闭原因(UTF-8 文本,不超过 125 字节)。
    • 发送后,该端点不再发送任何数据帧,但必须继续接收数据直到收到对方的 Close 帧。
  2. 对方回复确认

    • 接收方收到 Close 帧后,必须立即回复一个 Close 帧,且回复帧的状态码应与接收到的相同(通常如此)。
    • 回复的 Close 帧发送完成后,接收方也可以关闭底层 TCP 连接。
  3. 关闭 TCP 连接

    • 双方发送完 Close 帧后,等待一段时间(通常默认 2 秒)确保对方已收到,然后主动关闭 TCP 连接。
    • 若一方在发送 Close 帧后未收到对方的 Close 帧(例如网络故障),会超时后强制关闭连接。

为什么需要心跳检测?

心跳检测是一种保持连接活性的机制,通过定期发送少量数据(心跳包)来确认对方是否存活,并及时发现死连接。

  1. 检测对端是否存活:网络中断、进程崩溃等可能导致对方悄无声息地消失,心跳包可以快速发现。
  2. 防止中间设备断开连接:路由器、防火墙、负载均衡器等可能认为长时间无数据的连接已失效,主动断开。心跳包能保持连接活跃。
  3. 资源释放:及时关闭无效连接,释放服务端和客户端的资源。

websocket 客户端发送的请求中 请求头有哪些?

一、必须的请求头

  1. Upgrade ,表明客户端希望升级到 WebSocket 协议。 示例值 websocket
  2. Connection ,与 Upgrade 配合使用,指示当前连接需要升级。示例值 Upgrade
  3. Sec-WebSocket-Key,客户端随机生成的 16 字节 Base64 编码值,用于服务端校验握手合法性。
  4. Sec-WebSocket-Version,指定 WebSocket 协议版本,必须为 13(RFC 6455)。

二、可选的请求头

  1. Origin,请求来源,用于防止跨站 WebSocket 劫持(CSWSH)。服务端可据此进行安全校验。
  2. Sec-WebSocket-Protocol,客户端支持的子协议列表(用逗号分隔),服务端可从中选择一个通过响应头返回。用于应用层协议协商。
  3. Sec-WebSocket-Extensions,客户端希望使用的扩展(如压缩)。服务端可选择一个或全部,通过响应头返回。
  4. Host,服务端主机名和端口,与 HTTP 标准一致。
  5. Cookie,用于携带会话 Cookie,实现身份认证。
  6. Authorization,携带认证信息。

websocket 服务端响应头有哪些?

  1. Upgrade,确认升级协议。
  2. Connection,确认连接升级。
  3. Sec-WebSocket-Accept,根据客户端 Sec-WebSocket-Key 计算得出。
  4. Sec-WebSocket-Protocol,如果客户端请求了子协议,服务端选择其中一个返回。
  5. Sec-WebSocket-Extensions,如果客户端请求了扩展,服务端确认使用的扩展。

Websocket 扩展 | permessage-deflate

1、WHY ? WebSocket 协议通过扩展(Extensions) 机制来增强其核心功能,例如压缩数据以节省带宽、多路复用以减少连接数。

2、WHAT ?permessage-deflate 是 WebSocket 协议中最常用的扩展,它使用 DEFLATE 压缩算法(同 gzip)对 WebSocket 消息的载荷(payload) 进行压缩,从而显著减少网络传输的数据量,节省带宽并提升速度。

3、HOW?浏览器端的 WebSocket API 默认会尝试协商并使用 permessage-deflate ,只要服务器端也支持。不需要在客户端代码中做任何特殊配置。

http 复制代码
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
js 复制代码
const WebSocket = require("ws");
const wss = new WebSocket.Server({
  port: 5177,
  perMessageDeflate: true,
});

ws 库源码

js 复制代码
'use strict';

const WebSocket = require('./lib/websocket');

WebSocket.createWebSocketStream = require('./lib/stream');
WebSocket.Server = require('./lib/websocket-server');
WebSocket.Receiver = require('./lib/receiver');
WebSocket.Sender = require('./lib/sender');

WebSocket.WebSocket = WebSocket;
WebSocket.WebSocketServer = WebSocket.Server;

module.exports = WebSocket;

lib/websocket-server.js

js 复制代码
/**
 * Class representing a WebSocket server.
 *
 * @extends EventEmitter
 */
class WebSocketServer extends EventEmitter {
  /**
   * Create a `WebSocketServer` instance.
   *
   * @param {Object} options Configuration options
   * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether
   *     any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
   *     multiple times in the same tick
   * @param {Boolean} [options.autoPong=true] Specifies whether or not to
   *     automatically send a pong in response to a ping
   * @param {Number} [options.backlog=511] The maximum length of the queue of
   *     pending connections
   * @param {Boolean} [options.clientTracking=true] Specifies whether or not to
   *     track clients
   * @param {Number} [options.closeTimeout=30000] Duration in milliseconds to
   *     wait for the closing handshake to finish after `websocket.close()` is
   *     called
   * @param {Function} [options.handleProtocols] A hook to handle protocols
   * @param {String} [options.host] The hostname where to bind the server
   * @param {Number} [options.maxPayload=104857600] The maximum allowed message
   *     size
   * @param {Boolean} [options.noServer=false] Enable no server mode
   * @param {String} [options.path] Accept only connections matching this path
   * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable
   *     permessage-deflate
   * @param {Number} [options.port] The port where to bind the server
   * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S
   *     server to use
   * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
   *     not to skip UTF-8 validation for text and close messages
   * @param {Function} [options.verifyClient] A hook to reject connections
   * @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`
   *     class to use. It must be the `WebSocket` class or class that extends it
   * @param {Function} [callback] A listener for the `listening` event
   */
  constructor(options, callback) {
    super();

    options = {
      allowSynchronousEvents: true, // 是否允许同步事件触发
      autoPong: true, // 是否自动发送 pong 响应
      maxPayload: 100 * 1024 * 1024, // 最大消息大小,单位字节
      skipUTF8Validation: false, // 是否跳过 UTF-8 验证
      perMessageDeflate: false, // 是否启用消息压缩
      handleProtocols: null, // 协议处理函数,用于处理升级请求
      clientTracking: true, // 是否跟踪客户端连接
      closeTimeout: CLOSE_TIMEOUT, // 关闭握手时的超时时间,单位毫秒
      
      verifyClient: null, // 验证客户端连接的函数,用于拒绝连接

      noServer: false, // 是否启用无服务器模式

      backlog: null, // use default (511 as implemented in net.js)
      server: null, // 预创建的 HTTP/S 服务器实例
      host: null, // 主机名或 IP 地址,用于绑定服务器
      path: null, // 路径,用于匹配 WebSocket 连接请求
      port: null, // 端口号,用于绑定服务器
      WebSocket,
      ...options
    };

    // 确保用户在创建 WebSocket 服务器时,只能从三种模式中选择一种:
    // 1、监听指定端口、
    // 2、附着到已有 HTTP/HTTPS 服务器、
    // 3、不监听任何连接(手动处理升级)

    if (
      // 条件1:用户未指定端口,未指定 server,未指定 noServer
      (options.port == null && !options.server && !options.noServer) ||
      // 条件2:用户指定了端口,且指定了 server 或 noServer
      (options.port != null && (options.server || options.noServer)) ||
      // 条件3:用户指定了 server,且指定了 noServer
      (options.server && options.noServer)
    ) {
      // 有且只能指定一个选项,否则抛出 TypeError
      // 说明:用户只能选择一种模式,不能同时指定 port、server 和 noServer
      // 说明:用户可以指定 port 或 server,但不能同时指定 port 和 server
      throw new TypeError(
        'One and only one of the "port", "server", or "noServer" options ' +
          'must be specified'
      );
    }

    // 一、通过 port 创建新的 HTTP 服务器
    if (options.port != null) {
      // 通过 { port } 选项创建服务器时,会自动创建一个 HTTP 服务器并监听指定端口,
      // 同时该 HTTP 服务器对所有非 WebSocket 请求返回 426 Upgrade Required 状态码
      // 这个 HTTP 处理函数只会处理那些没有升级到 WebSocket 的普通 HTTP 请求。
      this._server = http.createServer((req, res) => {
        const body = http.STATUS_CODES[426];

        // 对于普通 HTTP 请求,返回 426 Upgrade Required 状态码,并附带纯文本响应体 "Upgrade Required"。
        res.writeHead(426, {
          'Content-Length': body.length,
          'Content-Type': 'text/plain'
        });
        res.end(body);
      });
      this._server.listen(
        options.port,
        options.host,
        options.backlog,
        callback
      );

      // 二、使用现有的 server 实例
    } else if (options.server) {
      this._server = options.server;
    }

    if (this._server) {
      const emitConnection = this.emit.bind(this, 'connection');

      this._removeListeners = addListeners(this._server, {
        listening: this.emit.bind(this, 'listening'),
        error: this.emit.bind(this, 'error'),
        // 处理 HTTP 升级请求,调用 handleUpgrade 方法
        upgrade: (req, socket, head) => {
          this.handleUpgrade(req, socket, head, emitConnection);
        }
      });
    }

    // 消息压缩
    if (options.perMessageDeflate === true) options.perMessageDeflate = {};

    // 客户端跟踪
    if (options.clientTracking) {
      this.clients = new Set();
      this._shouldEmitClose = false;
    }

    this.options = options;
    this._state = RUNNING;
  }

  /**
   * Returns the bound address, the address family name, and port of the server
   * as reported by the operating system if listening on an IP socket.
   * If the server is listening on a pipe or UNIX domain socket, the name is
   * returned as a string.
   *
   * @return {(Object|String|null)} The address of the server
   * @public
   */
  address() {
    if (this.options.noServer) {
      throw new Error('The server is operating in "noServer" mode');
    }

    if (!this._server) return null;
    return this._server.address();
  }

  /**
   * Stop the server from accepting new connections and emit the `'close'` event
   * when all existing connections are closed.
   * 停止服务器接受新连接并在所有现有连接关闭后触发 close 事件。
   *
   * @param {Function} [cb] A one-time listener for the `'close'` event
   * @public
   */
  close(cb) {
    // 如果服务器已关闭,直接触发 close 事件
    if (this._state === CLOSED) {
      if (cb) {
        // 注册一个一次性的 close 事件监听器
        this.once('close', () => {
          cb(new Error('The server is not running'));
        });
      }

      // 触发 close 事件
      process.nextTick(emitClose, this);
      return;
    }

    // 注册一个一次性的 close 事件监听器
    if (cb) this.once('close', cb);

    // 如果服务器已关闭,直接返回
    if (this._state === CLOSING) return;
    // 设置服务器状态为关闭中
    this._state = CLOSING;

    // 无服务器或外部服务器模式处理
    if (this.options.noServer || this.options.server) {
      if (this._server) {
        // 移除监听器并设置为 null
        this._removeListeners();
        this._removeListeners = this._server = null;
      }

      if (this.clients) {
        if (!this.clients.size) {
          process.nextTick(emitClose, this);
        } else {
          this._shouldEmitClose = true;
        }
      } else {
        process.nextTick(emitClose, this);
      }

      // 有服务器模式处理
    } else {
      const server = this._server;

      this._removeListeners();
      this._removeListeners = this._server = null;

      //
      // The HTTP/S server was created internally. Close it, and rely on its
      // `'close'` event.
      //
      server.close(() => {
        emitClose(this);
      });
    }
  }

  /**
   * See if a given request should be handled by this server instance.
   *
   * @param {http.IncomingMessage} req Request object to inspect
   * @return {Boolean} `true` if the request is valid, else `false`
   * @public
   */
  shouldHandle(req) {
    if (this.options.path) {
      const index = req.url.indexOf('?');
      const pathname = index !== -1 ? req.url.slice(0, index) : req.url;

      if (pathname !== this.options.path) return false;
    }

    return true;
  }

  /**
   * Handle a HTTP Upgrade request.
   * 负责处理 HTTP 升级请求,将标准 HTTP 连接转换为 WebSocket 连接,是实现 WebSocket 握手的关键环节。
   *
   * @param {http.IncomingMessage} req The request object
   * @param {Duplex} socket The network socket between the server and client
   * @param {Buffer} head The first packet of the upgraded stream
   * @param {Function} cb Callback
   * @public
   */
  handleUpgrade(req, socket, head, cb) {
    // 为 socket 添加错误监听器,确保连接错误时能够正确处理
    socket.on('error', socketOnError);

    // 提取关键请求头信息  sec-websocket-key、upgrade、sec-websocket-version
    const key = req.headers['sec-websocket-key']; // 用于安全握手的随机密钥
    const upgrade = req.headers.upgrade; // 升级协议标识
    const version = +req.headers['sec-websocket-version']; // WebSocket 协议版本

    // 非 GET 方法,直接返回 405 状态码
    if (req.method !== 'GET') {
      const message = 'Invalid HTTP method';
      abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
      return;
    }

    // 非 WebSocket 升级请求,直接返回 400 状态码
    if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
      const message = 'Invalid Upgrade header';
      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
      return;
    }

    // Sec-WebSocket-Key 头验证:确保 Sec-WebSocket-Key 头存在且值符合要求
    if (key === undefined || !keyRegex.test(key)) {
      const message = 'Missing or invalid Sec-WebSocket-Key header';
      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
      return;
    }

    // 版本要求 13 或 8
    if (version !== 13 && version !== 8) {
      const message = 'Missing or invalid Sec-WebSocket-Version header';
      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message, {
        'Sec-WebSocket-Version': '13, 8'
      });
      return;
    }

    // 路径不匹配
    if (!this.shouldHandle(req)) {
      abortHandshake(socket, 400);
      return;
    }

    // 处理子协议选择
    // 获取 Sec-WebSocket-Protocol 头值
    const secWebSocketProtocol = req.headers['sec-websocket-protocol'];
    let protocols = new Set();

    if (secWebSocketProtocol !== undefined) {
      try {
        // 解析 Sec-WebSocket-Protocol 头值
        protocols = subprotocol.parse(secWebSocketProtocol);
      } catch (err) {
        const message = 'Invalid Sec-WebSocket-Protocol header';
        abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
        return;
      }
    }

    // 扩展处理
    // 获取 Sec-WebSocket-Extensions 头值
    const secWebSocketExtensions = req.headers['sec-websocket-extensions'];
    const extensions = {};

    // 如果启用了消息压缩且请求包含扩展头
    if (
      this.options.perMessageDeflate &&
      secWebSocketExtensions !== undefined
    ) {
      const perMessageDeflate = new PerMessageDeflate(
        this.options.perMessageDeflate,
        true,
        this.options.maxPayload
      );

      try {
        // 解析 Sec-WebSocket-Extensions 头值
        const offers = extension.parse(secWebSocketExtensions);

        // 如果客户端支持消息压缩,创建并配置 PerMessageDeflate 实例
        if (offers[PerMessageDeflate.extensionName]) {
          perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
          extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
        }
      } catch (err) {
        const message =
          'Invalid or unacceptable Sec-WebSocket-Extensions header';
        abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
        return;
      }
    }

    //
    // Optionally call external client verification handler.
    // 客户端验证
    // 如果设置了验证函数,调用它进行验证
    if (this.options.verifyClient) {
      // 验证信息:提供包含 origin、安全性和请求对象的信息
      const info = {
        origin:
          req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
        secure: !!(req.socket.authorized || req.socket.encrypted),
        req
      };

      if (this.options.verifyClient.length === 2) {
        // 验证函数参数为 2 个,调用它进行验证
        this.options.verifyClient(info, (verified, code, message, headers) => {

          // 验证失败,返回 401 状态码
          if (!verified) {
            return abortHandshake(socket, code || 401, message, headers);
          }

          // 验证成功,继续升级握手
          this.completeUpgrade(
            extensions,
            key,
            protocols,
            req,
            socket,
            head,
            cb
          );
        });
        return;
      }

      // 验证函数参数为 3 个,调用它进行验证
      if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
    }

    // 完成升级握手
    this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
  }

  /**
   * Upgrade the connection to WebSocket.
   *
   * @param {Object} extensions The accepted extensions
   * @param {String} key The value of the `Sec-WebSocket-Key` header
   * @param {Set} protocols The subprotocols
   * @param {http.IncomingMessage} req The request object
   * @param {Duplex} socket The network socket between the server and client
   * @param {Buffer} head The first packet of the upgraded stream
   * @param {Function} cb Callback
   * @throws {Error} If called more than once with the same socket
   * @private
   */
  completeUpgrade(extensions, key, protocols, req, socket, head, cb) {
    //
    // Destroy the socket if the client has already sent a FIN packet.
    // 连接有效性检查:确保 socket 仍可读可写,否则销毁连接
    //
    if (!socket.readable || !socket.writable) return socket.destroy();

    // 重复处理检查:通过 kWebSocket 标记检查 socket 是否已被处理过,避免重复升级
    if (socket[kWebSocket]) {
      throw new Error(
        'server.handleUpgrade() was called more than once with the same ' +
          'socket, possibly due to a misconfiguration'
      );
    }

    // 服务器状态检查:确保服务器处于运行状态,否则返回 503 错误
    if (this._state > RUNNING) return abortHandshake(socket, 503);

    // 2、生成握手协议
    // 生成安全摘要:使用 SHA-1 算法对客户端提供的 key 和 GUID 进行哈希,生成 Sec-WebSocket-Accept 头
    const digest = createHash('sha1')
      .update(key + GUID)
      .digest('base64');

    // 构建响应头:创建 101 状态码的响应头,指示协议切换
    const headers = [
      'HTTP/1.1 101 Switching Protocols',
      'Upgrade: websocket',
      'Connection: Upgrade',
      `Sec-WebSocket-Accept: ${digest}`
    ];

    // 3、创建 WebSocket 实例 ()
    const ws = new this.options.WebSocket(null, undefined, this.options);

    // 3、处理子协议选择
    if (protocols.size) {
      //
      // Optionally call external protocol selection handler.
      //
      const protocol = this.options.handleProtocols
        ? this.options.handleProtocols(protocols, req)
        : protocols.values().next().value;
 
      if (protocol) {
        // 更新响应头:将选中的协议添加到响应头
        headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
        // 设置协议:在 WebSocket 实例上设置选中的协议
        ws._protocol = protocol;
      }
    }

    // 4、处理扩展
    if (extensions[PerMessageDeflate.extensionName]) {
      // 处理压缩扩展:如果启用了消息压缩,格式化扩展参数并添加到响应头
      const params = extensions[PerMessageDeflate.extensionName].params;
      const value = extension.format({
        [PerMessageDeflate.extensionName]: [params]
      });
      headers.push(`Sec-WebSocket-Extensions: ${value}`);
      // 设置扩展:在 WebSocket 实例上设置选中的扩展
      ws._extensions = extensions;
    }

    //
    // Allow external modification/inspection of handshake headers.
    // 触发 headers 事件:允许外部代码修改或检查握手响应头
    //
    this.emit('headers', headers, req);

    // 发送响应:将握手响应头写入 socket
    socket.write(headers.concat('\r\n').join('\r\n'));
    // 移除错误监听器:移除之前添加的临时错误监听器
    socket.removeListener('error', socketOnError);

    // 绑定 socket:将 socket 绑定到 WebSocket 实例
    ws.setSocket(socket, head, {
      allowSynchronousEvents: this.options.allowSynchronousEvents,
      maxPayload: this.options.maxPayload,
      skipUTF8Validation: this.options.skipUTF8Validation
    });

    if (this.clients) {
      // 添加到客户端集合:如果启用了客户端跟踪,将新连接添加到 clients Set
      this.clients.add(ws);
      // 设置关闭处理:监听 WebSocket 关闭事件,从客户端集合中移除,并在需要时触发服务器关闭事件
      ws.on('close', () => {
        this.clients.delete(ws);

        if (this._shouldEmitClose && !this.clients.size) {
          process.nextTick(emitClose, this);
        }
      });
    }

    // 通知完成:调用回调函数,传递 WebSocket 实例和请求对象
    cb(ws, req);
  }
}

lib/websocket.js

js 复制代码
/**
 * Class representing a WebSocket.
 *
 * @extends EventEmitter
 */
class WebSocket extends EventEmitter {
  /**
   * Create a new `WebSocket`.
   *
   * @param {(String|URL)} address The URL to which to connect
   * @param {(String|String[])} [protocols] The subprotocols
   * @param {Object} [options] Connection options
   */
  constructor(address, protocols, options) {
    super();

    this._binaryType = BINARY_TYPES[0]; // 二进制数据类型
    this._closeCode = 1006; // 关闭码
    this._closeFrameReceived = false; // 是否已接收关闭帧
    this._closeFrameSent = false; // 是否已发送关闭帧
    this._closeMessage = EMPTY_BUFFER; // 关闭消息缓冲区
    this._closeTimer = null; // 关闭定时器
    this._errorEmitted = false; // 是否已触发错误 事件
    this._extensions = {}; // 扩展列表
    this._paused = false; // 是否已暂停接收数据
    this._protocol = ''; // 子协议
    this._readyState = WebSocket.CONNECTING; // 连接状态
    this._receiver = null; // 接收器实例
    this._sender = null; // 发送器实例
    this._socket = null; // 底层 socket 实例

    // 客户端模式判断:当 address 不为 null 时,进入客户端模式
    if (address !== null) {
      this._bufferedAmount = 0; // 已缓冲数据量
      this._isServer = false; // 是否为服务器端
      this._redirects = 0; // 重定向次数

      if (protocols === undefined) {
        protocols = [];

      } else if (!Array.isArray(protocols)) {
        if (typeof protocols === 'object' && protocols !== null) {
          options = protocols;
          protocols = [];
        } else {
          protocols = [protocols];
        }
      }

      // 初始化客户端模式
      initAsClient(this, address, protocols, options);

      // 服务器模式初始化
    } else {
      this._autoPong = options.autoPong; // 是否自动回复 PONG 帧
      this._closeTimeout = options.closeTimeout; // 关闭超时时间
      this._isServer = true; // 是否为服务器端
    }
  }

  /**
   * For historical reasons, the custom "nodebuffer" type is used by the default
   * instead of "blob".
   *
   * @type {String}
   */
  get binaryType() {
    return this._binaryType;
  }

  set binaryType(type) {
    if (!BINARY_TYPES.includes(type)) return;

    this._binaryType = type;

    //
    // Allow to change `binaryType` on the fly.
    //
    if (this._receiver) this._receiver._binaryType = type;
  }

  /**
   * @type {Number}
   */
  get bufferedAmount() {
    if (!this._socket) return this._bufferedAmount;

    return this._socket._writableState.length + this._sender._bufferedBytes;
  }

  /**
   * @type {String}
   */
  get extensions() {
    return Object.keys(this._extensions).join();
  }

  /**
   * @type {Boolean}
   */
  get isPaused() {
    return this._paused;
  }

  /**
   * @type {Function}
   */
  /* istanbul ignore next */
  get onclose() {
    return null;
  }

  /**
   * @type {Function}
   */
  /* istanbul ignore next */
  get onerror() {
    return null;
  }

  /**
   * @type {Function}
   */
  /* istanbul ignore next */
  get onopen() {
    return null;
  }

  /**
   * @type {Function}
   */
  /* istanbul ignore next */
  get onmessage() {
    return null;
  }

  /**
   * @type {String}
   */
  get protocol() {
    return this._protocol;
  }

  /**
   * @type {Number}
   */
  get readyState() {
    return this._readyState;
  }

  /**
   * @type {String}
   */
  get url() {
    return this._url;
  }

  /**
   * Set up the socket and the internal resources.
   * 用于设置 WebSocket 连接的底层 socket,完成从 HTTP 连接到 WebSocket 连接的转换
   *
   * @param {Duplex} socket The network socket between the server and client
   * @param {Buffer} head The first packet of the upgraded stream
   * @param {Object} options Options object
   * @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
   *     any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
   *     multiple times in the same tick
   * @param {Function} [options.generateMask] The function used to generate the
   *     masking key
   * @param {Number} [options.maxPayload=0] The maximum allowed message size
   * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
   *     not to skip UTF-8 validation for text and close messages
   * @private
   */
  setSocket(socket, head, options) {
    // 初始化接收器,负责接收和解析 WebSocket 帧
    const receiver = new Receiver({
      // 允许同步事件
      allowSynchronousEvents: options.allowSynchronousEvents,
      binaryType: this.binaryType, // 二进制类型
      extensions: this._extensions, // 扩展
      isServer: this._isServer, // 是否为服务器端
      maxPayload: options.maxPayload, // 最大消息大小
      skipUTF8Validation: options.skipUTF8Validation // 是否跳过 UTF-8 验证
    });

    // 初始化发送器,负责发送 WebSocket 帧
    const sender = new Sender(socket, this._extensions, options.generateMask);

    // 保存引用,建立双向引用
    this._receiver = receiver;
    this._sender = sender;
    this._socket = socket;

    receiver[kWebSocket] = this;
    sender[kWebSocket] = this;
    socket[kWebSocket] = this;

    // 注册接收器事件监听器
    receiver.on('conclude', receiverOnConclude); // 处理连接结束
    receiver.on('drain', receiverOnDrain); // 处理缓冲区清空
    receiver.on('error', receiverOnError); // 处理错误
    receiver.on('message', receiverOnMessage); // 处理消息
    receiver.on('ping', receiverOnPing); // 处理 ping 帧
    receiver.on('pong', receiverOnPong); // 处理 pong 帧

    // 注册发送器错误处理
    sender.onerror = senderOnError;

    //
    // These methods may not be available if `socket` is just a `Duplex`.
    //
    if (socket.setTimeout) socket.setTimeout(0); // 设置超时时间为 0,禁用超时
    if (socket.setNoDelay) socket.setNoDelay(); // 禁用 Nagle 算法

    // 如果 head 中有数据(通常是 HTTP 升级后的剩余数据),将其推回 socket 缓冲区,以便接收器处理
    if (head.length > 0) socket.unshift(head);

    // 监听底层 socket 事件
    socket.on('close', socketOnClose);
    socket.on('data', socketOnData);
    socket.on('end', socketOnEnd); // 处理 socket 结束
    socket.on('error', socketOnError);

    this._readyState = WebSocket.OPEN;
    this.emit('open'); // 触发 open 事件,通知连接已打开
  }

  /**
   * Emit the `'close'` event.
   * 用于触发 WebSocket 关闭事件并清理相关资源
   *
   * @private
   */
  emitClose() {
    // 无 socket 情况处理
    if (!this._socket) {
      this._readyState = WebSocket.CLOSED; // 标记为已关闭
      // 触发事件:触发 close 事件,传递关闭码和关闭消息
      this.emit('close', this._closeCode, this._closeMessage);
      return;
    }

    if (this._extensions[PerMessageDeflate.extensionName]) {
      // 清理扩展
      this._extensions[PerMessageDeflate.extensionName].cleanup();
    }

    this._receiver.removeAllListeners(); // 移除监听器
    this._readyState = WebSocket.CLOSED; // 标记为已关闭
    // 触发事件:触发 close 事件,传递关闭码和关闭消息
    this.emit('close', this._closeCode, this._closeMessage);
  }

  /**
   * 用于正常关闭 WebSocket 连接,通过发送关闭帧并等待对方确认,实现标准的 WebSocket 关闭握手过程。
   * Start a closing handshake.
   *
   *          +----------+   +-----------+   +----------+
   *     - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
   *    |     +----------+   +-----------+   +----------+     |
   *          +----------+   +-----------+         |
   * CLOSING  |ws.close()|<--|close frame|<--+-----+       CLOSING
   *          +----------+   +-----------+   |
   *    |           |                        |   +---+        |
   *                +------------------------+-->|fin| - - - -
   *    |         +---+                      |   +---+
   *     - - - - -|fin|<---------------------+
   *              +---+
   *
   * @param {Number} [code] Status code explaining why the connection is closing
   * @param {(String|Buffer)} [data] The reason why the connection is
   *     closing
   * @public
   */
  close(code, data) {
    // 如果 WebSocket 已关闭,直接返回
    if (this.readyState === WebSocket.CLOSED) return;

    // 如果 WebSocket 正在连接中,
    if (this.readyState === WebSocket.CONNECTING) {
      const msg = 'WebSocket was closed before the connection was established';
      // 中断握手过程,传递错误信息
      abortHandshake(this, this._req, msg);
      return;
    }

    // 如果 WebSocket 正在关闭中,
    if (this.readyState === WebSocket.CLOSING) {
      if (
        // 已发送了关闭帧
        this._closeFrameSent &&
        // 已经收到了对方的关闭帧 或者 接收器发生了错误
        (this._closeFrameReceived || this._receiver._writableState.errorEmitted)
      ) {
        // 结束底层 socket 连接
        this._socket.end();
      }

      return;
    }

    this._readyState = WebSocket.CLOSING;
    // 发送关闭帧
    this._sender.close(code, data, !this._isServer, (err) => {
      //
      // This error is handled by the `'error'` listener on the socket. We only
      // want to know if the close frame has been sent here.
      //
      // 如果发送过程中出现错误,直接返回
      if (err) return;

      this._closeFrameSent = true; // 标记为已发送关闭帧

      if (
        this._closeFrameReceived || // 已经收到了对方的关闭帧
        this._receiver._writableState.errorEmitted // 接收器发生了错误
      ) {
        // 结束底层 socket 连接
        this._socket.end();
      }
    });

    // 设置关闭定时器
    setCloseTimer(this);
  }

  /**
   * Pause the socket.
   *
   * @public
   */
  pause() {
    // 如果 WebSocket 正在连接中或已关闭,直接返回
    if (
      this.readyState === WebSocket.CONNECTING ||
      this.readyState === WebSocket.CLOSED
    ) {
      return;
    }

    this._paused = true; // 标记为已暂停
    // 暂停 socket 的写入操作
    this._socket.pause();
  }

  /**
   * Send a ping.
   * 用于发送 WebSocket ping 帧,
   * 主要用于测试连接是否活跃、作为心跳机制的一部分,以及防止连接因超时而被关闭。
   *
   * @param {*} [data] The data to send
   * @param {Boolean} [mask] Indicates whether or not to mask `data`
   * @param {Function} [cb] Callback which is executed when the ping is sent
   * @public
   */
  ping(data, mask, cb) {
    // 抛出错误:如果 WebSocket 正在连接中,不能发送 ping 消息
    if (this.readyState === WebSocket.CONNECTING) {
      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
    }

    // 参数重载处理
    if (typeof data === 'function') {
      cb = data; // 如果 data 是一个函数,将其作为回调函数
      data = mask = undefined; // 将 data 和 mask 设置为 undefined
    } else if (typeof mask === 'function') {
      cb = mask; // 如果 mask 是一个函数,将其作为回调函数
      mask = undefined; // 将 mask 设置为 undefined
    }

    // 如果 data 是一个数字,将其转换为字符串
    if (typeof data === 'number') data = data.toString();

    // 如果 WebSocket 不是已打开状态,将数据发送到关闭队列
    if (this.readyState !== WebSocket.OPEN) {
      sendAfterClose(this, data, cb);
      return;
    }

    // 如果 mask 未定义,根据是否是服务器端来决定默认值
    // 客户端:mask = true(客户端发送的帧需要掩码)
    // 服务器端:mask = false(服务器发送的帧不需要掩码)
    if (mask === undefined) mask = !this._isServer;

    // 发送 ping 帧
    this._sender.ping(data || EMPTY_BUFFER, mask, cb);
  }

  /**
   * Send a pong.
   * 用于发送 WebSocket pong 帧,
   * 主要用于响应 ping 帧或作为心跳机制的一部分,维持 WebSocket 连接的活跃状态。
   *
   * @param {*} [data] The data to send
   * @param {Boolean} [mask] Indicates whether or not to mask `data`
   * @param {Function} [cb] Callback which is executed when the pong is sent
   * @public
   */
  pong(data, mask, cb) {
    // 抛出错误:如果 WebSocket 正在连接中,不能发送 pong 消息
    if (this.readyState === WebSocket.CONNECTING) {
      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
    }

    // 参数重载处理
    if (typeof data === 'function') {
      cb = data; // 如果 data 是一个函数,将其作为回调函数
      data = mask = undefined; // 将 data 和 mask 设置为 undefined
    } else if (typeof mask === 'function') {
      cb = mask; // 如果 mask 是一个函数,将其作为回调函数
      mask = undefined; // 将 mask 设置为 undefined
    }

    // 如果 data 是一个数字,将其转换为字符串
    if (typeof data === 'number') data = data.toString();

    // 如果 WebSocket 不是已打开状态,将数据发送到关闭队列
    if (this.readyState !== WebSocket.OPEN) {
      sendAfterClose(this, data, cb);
      return;
    }

    // 如果 mask 未定义,根据是否是服务器端来决定默认值
    // 客户端:mask = true(客户端发送的帧需要掩码)
    // 服务器端:mask = false(服务器发送的帧不需要掩码)
    if (mask === undefined) mask = !this._isServer;

    this._sender.pong(data || EMPTY_BUFFER, mask, cb);
  }

  /**
   * Resume the socket.
   *
   * @public
   */
  resume() {
    // 连接状态检查:如果 WebSocket 已关闭或正在连接,直接返回
    if (
      this.readyState === WebSocket.CONNECTING ||
      this.readyState === WebSocket.CLOSED
    ) {
      return;
    }

    // 将暂停状态设置为 false
    this._paused = false;
    // 如果接收缓冲区不需要刷新,恢复 socket 连接
    if (!this._receiver._writableState.needDrain) this._socket.resume();
  }

  /**
   * Send a data message.
   *
   * @param {*} data The message to send
   * @param {Object} [options] Options object
   * @param {Boolean} [options.binary] Specifies whether `data` is binary or
   *     text
   * @param {Boolean} [options.compress] Specifies whether or not to compress
   *     `data`
   * @param {Boolean} [options.fin=true] Specifies whether the fragment is the
   *     last one
   * @param {Boolean} [options.mask] Specifies whether or not to mask `data`
   * @param {Function} [cb] Callback which is executed when data is written out
   * @public
   */
  send(data, options, cb) {
    if (this.readyState === WebSocket.CONNECTING) {
      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
    }

    if (typeof options === 'function') {
      cb = options;
      options = {};
    }

    if (typeof data === 'number') data = data.toString();

    if (this.readyState !== WebSocket.OPEN) {
      sendAfterClose(this, data, cb);
      return;
    }

    const opts = {
      binary: typeof data !== 'string',
      mask: !this._isServer,
      compress: true,
      fin: true,
      ...options
    };

    if (!this._extensions[PerMessageDeflate.extensionName]) {
      opts.compress = false;
    }

    this._sender.send(data || EMPTY_BUFFER, opts, cb);
  }

  /**
   * Forcibly close the connection.
   * 强制终止websocket连接,不经过正常的关闭握手过程,直接销毁底层的 socket 连接。
   *
   * @public
   */
  terminate() {
    // 连接状态检查:如果 WebSocket 已关闭或正在连接,直接返回
    if (this.readyState === WebSocket.CLOSED) return;
    if (this.readyState === WebSocket.CONNECTING) {
      const msg = 'WebSocket was closed before the connection was established';
      abortHandshake(this, this._req, msg);
      return;
    }

    // 如果 WebSocket 已经建立连接
    if (this._socket) {
      this._readyState = WebSocket.CLOSING;
      // 销毁底层的 socket 连接
      this._socket.destroy();
    }
  }
}

最后

  1. 浏览器 Websocket
  2. websocket 文档
  3. customElements自定义元素
  4. EventSource SSE
  5. node http
  6. node net
  7. node tls
  8. node EventEmitter
相关推荐
米丘2 小时前
Node.js 事件循环
前端·javascript·node.js
Kel2 小时前
深入 Ink 源码:当 React 遇见终端 —— Custom Reconciler 全链路剖析
react.js·架构·node.js
全马必破三4 小时前
Vue3+Node.js 实现AI流式输出全解析
前端·javascript·node.js
吴声子夜歌5 小时前
Node.js——util工具模块
node.js
A.A呐6 小时前
【Linux第二十一章】http
linux·运维·http
笑笑先生6 小时前
从接口搬运工到研发控制平面,BFF 到底在解决什么?
前端·架构·node.js
www_stdio6 小时前
🚀 从 Event Loop 到 AI Agent:我的 Node.js 全栈进阶之路
前端·node.js·nestjs
白毛大侠6 小时前
WebSocket 核心:借 HTTP 建联,做自己的通信
websocket·网络协议·http
吴声子夜歌6 小时前
Node.js——fs文件系统模块
node.js