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

0 阅读14分钟

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

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

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

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

HTTP

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

image.png

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

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

创建 TCP 服务器

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

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}`);
});
net.createServer([options][, connectionListener])

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

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}`);
});

客户端

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)

  <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>

image.png

image.png

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

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

image.png

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

支持发送哪些数据类型?

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

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

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

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

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. 不监听任何连接(手动处理升级)
// 模式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 状态码响应,完成握手过程。

// 构建响应头:创建 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,只要服务器端也支持。不需要在客户端代码中做任何特殊配置。

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

image.png

ws 库源码

'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

/**
 * 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

/**
 * 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