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(字符串)BlobArrayBufferArrayBufferView(如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 服务器时,只能从三种模式中选择一种:
- 监听指定端口。
- 附着到已有 HTTP/HTTPS 服务器。
- 不监听任何连接(手动处理升级)
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 关闭握手过程
-
任意一方发起关闭
- 端点(客户端或服务器)发送一个 Close 控制帧 (opcode =
0x08)。 - 该帧可以包含一个 状态码 (如
1000表示正常关闭)和可选的 关闭原因(UTF-8 文本,不超过 125 字节)。 - 发送后,该端点不再发送任何数据帧,但必须继续接收数据直到收到对方的 Close 帧。
- 端点(客户端或服务器)发送一个 Close 控制帧 (opcode =
-
对方回复确认
- 接收方收到 Close 帧后,必须立即回复一个 Close 帧,且回复帧的状态码应与接收到的相同(通常如此)。
- 回复的 Close 帧发送完成后,接收方也可以关闭底层 TCP 连接。
-
关闭 TCP 连接
- 双方发送完 Close 帧后,等待一段时间(通常默认 2 秒)确保对方已收到,然后主动关闭 TCP 连接。
- 若一方在发送 Close 帧后未收到对方的 Close 帧(例如网络故障),会超时后强制关闭连接。
为什么需要心跳检测?
心跳检测是一种保持连接活性的机制,通过定期发送少量数据(心跳包)来确认对方是否存活,并及时发现死连接。
- 检测对端是否存活:网络中断、进程崩溃等可能导致对方悄无声息地消失,心跳包可以快速发现。
- 防止中间设备断开连接:路由器、防火墙、负载均衡器等可能认为长时间无数据的连接已失效,主动断开。心跳包能保持连接活跃。
- 资源释放:及时关闭无效连接,释放服务端和客户端的资源。
websocket 客户端发送的请求中 请求头有哪些?
一、必须的请求头
Upgrade,表明客户端希望升级到 WebSocket 协议。 示例值websocket。Connection,与Upgrade配合使用,指示当前连接需要升级。示例值Upgrade。Sec-WebSocket-Key,客户端随机生成的 16 字节 Base64 编码值,用于服务端校验握手合法性。Sec-WebSocket-Version,指定 WebSocket 协议版本,必须为13(RFC 6455)。
二、可选的请求头
Origin,请求来源,用于防止跨站 WebSocket 劫持(CSWSH)。服务端可据此进行安全校验。Sec-WebSocket-Protocol,客户端支持的子协议列表(用逗号分隔),服务端可从中选择一个通过响应头返回。用于应用层协议协商。Sec-WebSocket-Extensions,客户端希望使用的扩展(如压缩)。服务端可选择一个或全部,通过响应头返回。Host,服务端主机名和端口,与 HTTP 标准一致。Cookie,用于携带会话 Cookie,实现身份认证。Authorization,携带认证信息。
websocket 服务端响应头有哪些?
Upgrade,确认升级协议。Connection,确认连接升级。Sec-WebSocket-Accept,根据客户端Sec-WebSocket-Key计算得出。Sec-WebSocket-Protocol,如果客户端请求了子协议,服务端选择其中一个返回。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();
}
}
}