本文深入解析 Node.js 中 WebSocket 的底层实现机制,通过从零构建服务端与客户端示例,展示基于 ws 模块的全双工通信流程。内容涵盖协议升级、消息收发、二进制数据处理及常见错误排查,帮助开发者掌握无需框架依赖的 WebSocket 核心原理。
WebSockets 是一种网络通信协议,旨在实现客户端与服务器之间的双向通信。
该协议通常应用于需要即时数据更新的应用场景,通过建立在 HTTP 之上的持久双工通道来支持实时交互,从而避免了频繁的连接协商开销。服务器主动推送数据是 WebSockets 的典型应用场景之一。
本文将从代码层面出发,分别探讨 WebSocket 通信两端的技术实现:服务端采用 Node.js,客户端则使用原生 JavaScript。
WebSocket 协议
在过去,若要在浏览器中通过 HTTP 实现双工通信或服务器推送,往往需要借助各种变通技巧。如今,WebSocket 已成为 HTTP 协议的正式组成部分,其本质是将普通 HTTP 连接“升级”为 WebSocket 连接。
利用 WebSocket,浏览器客户端与后端服务器之间可以双向传输任意数据。通信双方均可主动发起新消息,这为各类需要持续通信或广播的实时应用提供了基础设施。目前,开发者广泛利用 WebSocket 协议开发游戏、聊天应用、直播系统及协作工具等,应用场景极为丰富。
为了便于理解,本文将构建一个简易的服务端与客户端,并通过它们深入剖析 WebSocket 通信过程中的具体细节。
创建简易服务端
首先,你需要创建一个包含 /server 和 /client 两个子目录的项目根目录。随后,编写一个极简的 Node.js 服务器,用于建立 WebSocket 连接并回显接收到的任何数据。进入 /websockets/server 目录并初始化新项目。
$ npm init接下来,我们需要安装 ws 模块以支持 WebSocket 功能。
$ npm install ws完成依赖安装后,我们可以编写一个简单的回显服务器,代码保存为 echo.js。
// echo.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log('Received message:', message);
ws.send(message); // Echo the message back to the client
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
console.log(‘server started');在上述代码中,我们监听 3000 端口,并监听 WebSocket.Server 对象上的 connection 事件。一旦建立连接,回调函数会传入一个套接字对象(ws)。利用该对象,我们进一步监听 message 和 close 两个事件。
每当客户端发送消息时,系统会调用 onMessage 处理程序并将消息内容传入。在该处理程序中,我们调用 ws.send() 方法将接收到的消息原样回显给客户端。
需要注意的是,ws.send() 方法允许我们在任意时刻主动发送消息。因此,我们可以根据其他事件(如服务端的更新或来自其他客户端的消息)向客户端推送状态更新。
onClose 处理程序用于在客户端断开连接时执行相应操作。在本示例中,我们仅记录断开连接的事件日志。
测试套接字服务器
如果能通过命令行简便地测试套接字服务器,将极大提升开发效率。Websocat 工具非常适合此目的。其安装过程简单,具体步骤可参考相关文档,且网上有许多使用示例可供参考。
现在启动服务器进程。
/websockets/server $ node echo.js
使用 Ctrl-z 将进程挂起至后台,然后执行 $ bg 命令使其在后台继续运行,最后运行以下命令进行测试。
$ ./websocat.x86_64-unknown-linux-musl -t --ws-c-uri=wss://localhost:3000/ - ws-c:cmd:'socat - ssl:echo.websocket.org:443,verify=0'上述操作将建立一个开放的 WebSocket 连接,允许你在控制台输入内容并查看服务器响应。你将看到如下交互过程。
$ node echo.js
Server started
^Z
[1]+ Stopped node echo.js
matthewcarltyson@dev3:~/websockets/server$ bg
[1]+ node echo.js &
matthewcarltyson@dev3:~/websockets/server$ ./websocat.x86_64-unknown-linux-musl -t --ws-c-uri=wss://localhost:3000/ - ws-c:cmd:'socat - ssl:echo.websocket.org:443,verify=0'
Request served by 7811941c69e658
An echo test
An echo test
Works
Works
^C
matthewcarltyson@dev3:~/websockets/server$ fg
node echo.js
^C创建客户端
接下来,进入 /websockets/client 目录,创建一个用于与服务端交互的网页。保持服务端在后台运行,我们从客户端侧访问服务端。
首先,创建一个名为 index.html 的文件。
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Client</title>
</head>
<body>
<h1>WebSocket Client</h1>
<input type="text" id="message" placeholder="Enter message">
<button id="send-btn">Send</button>
<div id="output"></div>
<script src="script.js"></script>
</body>
</html>该文件仅包含一个文本输入框和一个提交按钮,本身不执行任何逻辑,仅提供我们在后续脚本文件中所需的 DOM 元素。
// script.js
const wsUri = "ws://localhost:3000";
const outputDiv = document.getElementById("output");
const messageInput = document.getElementById("message");
const sendButton = document.getElementById("send-btn");
let websocket;
function connect() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (event) {
outputDiv.innerHTML += "
Connected to server!
";
};
websocket.onmessage = function (event) {
const receivedMessage = event.data;
outputDiv.innerHTML += "
Received: " + receivedMessage + "
";
};
websocket.onerror = function (event) {
outputDiv.innerHTML += "
Error: " + event.error + "
";
};
websocket.onclose = function (event) {
outputDiv.innerHTML += "
Connection closed.
";
};
}
sendButton.addEventListener("click", function () {
const message = messageInput.value;
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send(message);
messageInput.value = "";
} else {
outputDiv.innerHTML += "
Error: Connection not open.
";
}
});
connect(); // Connect immediately此脚本利用浏览器原生 API 设置了多个事件处理程序。脚本加载后,立即启动 WebSocket 连接,并监听 open、onclose、onmessage 和 onerror 事件。
每个事件触发时,都会将相应的状态更新追加到 DOM 中。其中最重要的是 onmessage 事件,我们在此接收来自服务器的消息并将其显示出来。
按钮的点击处理程序获取用户输入的文本(messageInput.value),并通过 WebSocket 对象的 send() 函数将其发送至服务器。发送完成后,我们将输入框的值重置为空字符串。
假设后端服务仍在运行且可通过 ws://localhost:3000 访问,我们现在可以启动前端应用。我们可以使用 http-server 作为运行前端的简便方式。
这是一种在 Web 服务器中托管静态文件的简易方法,类似于 Python 的 http 模块或 Java 的简单 Web 服务器,但专为 Node.js 环境设计。
该工具可作为全局 NPM 包安装,也可以直接从客户端目录通过 npx 运行。
/websockets/client/ $ npx http-server -o当我们执行上一个命令并访问该页面时,会看到预期的表单界面。然而,当我们在输入框中输入消息并点击发送时,页面显示如下结果。
Received: [object Blob]ws如果您查看浏览器开发控制台,所有内容均通过 WebSocket 通道(即 network 选项卡)进行传输。问题在于,为什么返回的数据以 Blob 对象的形式呈现?
查看服务端控制台,输出显示如下内容。
Client connected
Received message: <Buffer 6f 6d 20 6d 61 6e 69 20 70 61 64 6d 65 20 68 75 6d>由此可知,问题出在服务端。原因在于 ws 模块的较新版本不再自动将消息解码为字符串,而是直接提供二进制缓冲区。以下是修复 echo.js 中 onmessage 处理程序的快速方案。
ws.on('message', (message, isBinary) => {
message = isBinary ? message : message.toString();
console.log('Received message:', message);
ws.send(message);
});
我们在回调函数中使用第二个参数 isBinary 进行判断。如果处理程序接收到的是二进制数据,则使用 message.toString() 快速将其转换为字符串。
这篇快速指南阐明了 WebSocket 客户端-服务器通信的底层机制,避免了框架带来的复杂性干扰。
由此可见,使用 WebSocket 高级功能的基础非常简单。只需利用简单的回调和消息发送机制,即可通过浏览器标准 API 和流行的 Node.js 库实现全双工和异步通信。
当然,在诸多实际项目中,你可能希望在前端使用 React 等框架,在后端使用 Node.js 或其他运行时环境。幸运的是,一旦掌握了基础知识,将这些框架集成进来便轻而易举。
本文的讨论和示例有意忽略了安全性问题。正如 Web 开发的各个领域一样,安全性增加了一层复杂性,需要在堆栈的两端进行专门管理。
可扩展性和错误处理是我们在实际 WebSocket 实现中必须解决的其他关键问题。
到此这篇关于 Node.js 中 WebSocket 底层实现的文章就介绍到这了,更多相关 Node.js WebSocket 底层内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!