Node.js WebSocket 实战:实时通信封装方案
🚀Node.js WebSocket实战:从零搭建实时通信,新手也能懂的封装方案
在Web开发中,实时通信是很多场景的刚需,比如在线聊天、实时数据看板、多人协作编辑等。而WebSocket作为实现双向实时通信的核心技术,让服务器和客户端可以随时主动向对方发送数据,彻底解决了HTTP协议“请求-响应”模式的局限性。
今天就带大家用Node.js从零搭建一个简易的WebSocket服务器,并通过公共封装的方式,让代码结构清晰、易于维护和扩展。全程步骤详细,代码可直接复用,新手也能轻松上手!
📌 核心目标(新手先明确)
-
基础搭建:使用
ws库快速创建一个WebSocket服务器,实现客户端与服务器的连接、消息收发。 -
公共封装:将WebSocket的核心逻辑(如连接管理、消息广播、错误处理)封装成独立模块,避免业务代码与底层实现耦合。
-
实战落地:实现一个简单的“聊天室”功能,支持新用户上线通知、用户下线通知、群聊消息广播。
-
客户端演示:编写一个极简的HTML页面作为客户端,用于测试和演示。
🛠 环境准备
首先,我们需要一个Node.js环境。然后,创建一个新的项目文件夹并初始化:
# 创建项目文件夹并进入
mkdir nodejs-websocket-demo && cd nodejs-websocket-demo
# 初始化 package.json
npm init -y
# 安装核心依赖:ws 库(一个高性能、易用的 WebSocket 实现)
npm install ws --save
一、项目目录结构设计(结构化是封装的基础)
一个好的项目结构能让我们的代码更有条理。我们将遵循“关注点分离”的原则来设计目录:
nodejs-websocket-demo/
├── server/ # 服务器端代码
│ ├── index.js # 服务入口(启动HTTP和WebSocket服务)
│ ├── websocket/ # WebSocket核心逻辑封装
│ │ ├── index.js # WebSocket管理器(公共封装核心)
│ │ └── connection.js # 单个连接的封装(可选,用于更复杂的场景)
├── public/ # 客户端静态资源
│ └── index.html # 极简的HTML客户端
└── package.json # 项目依赖
二、核心封装:WebSocket管理器 (websocket/index.js)
这是本次实战的核心。我们将创建一个WebSocketManager类,它负责管理所有客户端连接,并提供统一的方法来发送和广播消息。
// server/websocket/index.js
constWebSocket = require('ws');
/**
* WebSocket 管理器
* 封装了连接管理、消息广播等公共逻辑
*/
classWebSocketManager {
/**
* 构造函数
* @param {http.Server} server - HTTP服务器实例,用于共享端口
*/
constructor(server) {
// 创建 WebSocket 服务器实例,并挂载到 HTTP 服务器上
this.wss = newWebSocket.Server({ server });
// 存储所有已连接的客户端 socket
this.clients = newSet();
// 绑定事件监听器
this.bindEvents();
console.log('✅ WebSocket 管理器已初始化,等待客户端连接...');
}
/**
* 绑定 WebSocket 服务器事件
*/
bindEvents() {
// 1. 监听 'connection' 事件:当有新客户端连接时触发
this.wss.on('connection', (ws, req) => {
const clientIp = req.socket.remoteAddress;
console.log(`🔌 新客户端连接: ${clientIp}`);
// 将新连接的 socket 添加到客户端集合中
this.clients.add(ws);
// 向所有客户端广播新用户上线的消息
this.broadcast({
type: 'SYSTEM',
message: '有新用户加入了聊天室!'
});
// 2. 监听单个连接的 'message' 事件:当接收到客户端消息时触发
ws.on('message', (message) => {
console.log(`📥 收到消息: ${message}`);
try {
// 假设消息是JSON格式
const data = JSON.parse(message);
// 广播收到的消息给所有客户端
this.broadcast({
type: 'MESSAGE',
username: data.username || '匿名用户',
message: data.message
});
} catch (e) {
console.error('❌ 消息解析失败:', e);
// 如果消息不是JSON,也原样广播
this.broadcast({
type: 'RAW',
message: message.toString()
});
}
});
// 3. 监听单个连接的 'close' 事件:当客户端断开连接时触发
ws.on('close', () => {
console.log(`🔌 客户端断开连接: ${clientIp}`);
// 从客户端集合中移除断开的 socket
this.clients.delete(ws);
// 向所有客户端广播用户下线的消息
this.broadcast({
type: 'SYSTEM',
message: '有用户离开了聊天室。'
});
});
// 4. 监听单个连接的 'error' 事件:当连接发生错误时触发
ws.on('error', (error) => {
console.error(`❌ WebSocket 错误:`, error);
// 错误发生后,通常连接会自动关闭,'close'事件会被触发
});
});
}
/**
* 向所有已连接的客户端广播消息
* @param {Object|string} data - 要广播的数据(可以是对象或字符串)
*/
broadcast(data) {
const message = typeof data === 'string' ? data : JSON.stringify(data);
// 遍历所有客户端,逐一发送消息
this.clients.forEach((client) => {
// 确保客户端连接是打开状态
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
console.log(`📤 广播消息给 ${this.clients.size} 个客户端: ${message}`);
}
/**
* 关闭 WebSocket 服务器
*/
close() {
this.wss.close(() => {
console.log('WebSocket 服务器已关闭。');
});
}
}
module.exports = WebSocketManager;
三、创建服务入口 (server/index.js)
现在,我们来创建服务的入口文件。这里我们将同时启动一个HTTP服务器(用于提供客户端HTML页面)和我们刚刚封装好的WebSocket服务器。
// server/index.js
const http = require('http');
const fs = require('fs');
const path = require('path');
constWebSocketManager = require('./websocket');
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
// 当访问根路径时,返回客户端 HTML 页面
if (req.url === '/') {
const filePath = path.join(__dirname, '../public/index.html');
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end('404 Not Found');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
});
}
});
// 创建并初始化 WebSocket 管理器
// 将 HTTP 服务器实例传入,这样 WebSocket 就可以和 HTTP 服务共享同一个端口(例如 3000)
const wssManager = newWebSocketManager(server);
// 启动服务器,监听 3000 端口
constPORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`🚀 HTTP 和 WebSocket 服务已启动,访问 http://localhost:${PORT}`);
});
// 捕获进程退出信号,优雅地关闭服务
process.on('SIGINT', () => {
console.log('\n正在关闭服务...');
wssManager.close();
server.close(() => {
console.log('HTTP 服务器已关闭。');
process.exit(0);
});
});
四、编写客户端 (public/index.html)
最后,我们需要一个客户端来测试我们的服务器。这个HTML页面非常简单,包含一个输入框、一个发送按钮和一个用于显示消息的区域。
<!-- public/index.html -->
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>Node.js WebSocket 聊天室</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#messages { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
#messagesdiv { margin-bottom: 8px; padding: 8px; border-radius: 4px; }
#messages.system { background-color: #f0f0f0; color: #666; font-style: italic; }
#messages.user { background-color: #e3f2fd; }
#messageInput { width: calc(100% - 120px); padding: 8px; }
#sendBtn { width: 100px; padding: 8px; }
</style>
</head>
<body>
<h1>简易聊天室</h1>
<divid="messages"></div>
<inputtype="text"id="usernameInput"placeholder="请输入你的昵称"value="用户">
<inputtype="text"id="messageInput"placeholder="请输入消息...">
<buttonid="sendBtn">发送</button>
<script>
// 连接到 WebSocket 服务器
const ws = newWebSocket('ws://localhost:3000');
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const usernameInput = document.getElementById('usernameInput');
// 1. 连接成功时触发
ws.onopen = () => {
console.log('✅ 已连接到 WebSocket 服务器');
appendMessage('系统:已成功连接到聊天室!', 'system');
};
// 2. 接收到服务器消息时触发
ws.onmessage = (event) => {
console.log(`📥 收到消息: ${event.data}`);
const data = JSON.parse(event.data);
if (data.type === 'SYSTEM') {
appendMessage(`系统:${data.message}`, 'system');
} elseif (data.type === 'MESSAGE') {
appendMessage(`${data.username}: ${data.message}`, 'user');
} else {
appendMessage(`原始消息: ${data.message}`, 'system');
}
};
// 3. 连接关闭时触发
ws.onclose = () => {
console.log('🔌 与服务器的连接已关闭');
appendMessage('系统:与服务器的连接已断开...', 'system');
};
// 4. 连接发生错误时触发
ws.onerror = (error) => {
console.error('❌ WebSocket 错误:', error);
appendMessage(`系统:连接出错!${error.message}`, 'system');
};
// 发送消息
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
const username = usernameInput.value.trim() || '匿名用户';
if (message && ws.readyState === WebSocket.OPEN) {
// 发送JSON格式的消息
ws.send(JSON.stringify({ username, message }));
messageInput.value = '';
}
});
// 按下回车键发送消息
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendBtn.click();
}
});
// 辅助函数:在页面上追加消息
functionappendMessage(text, type = '') {
const messageDiv = document.createElement('div');
messageDiv.textContent = text;
if (type) {
messageDiv.classList.add(type);
}
messagesDiv.appendChild(messageDiv);
// 自动滚动到底部
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
五、启动与测试
一切准备就绪!现在让我们来启动服务并体验一下。
- 启动服务器
在项目根目录下运行:
node server/index.js
如果一切顺利,你会在控制台看到:
✅ WebSocket 管理器已初始化,等待客户端连接...
🚀 HTTP 和 WebSocket 服务已启动,访问 http://localhost:3000
- 打开客户端
打开你的浏览器,访问 http://localhost:3000。你会看到一个简单的聊天室界面。
-
体验实时通信
-
单窗口测试:在输入框中输入消息,点击“发送”,你会看到消息显示在消息区域。
-
多窗口测试(核心):打开另一个浏览器窗口或标签页,同样访问
http://localhost:3000。-
当第二个窗口打开时,第一个窗口会收到“系统:有新用户加入了聊天室!”的提示。
-
在任意一个窗口发送消息,两个窗口都会同时收到这条消息,这就是我们实现的广播功能。
-
关闭其中一个窗口,另一个窗口会收到“系统:有用户离开了聊天室。”的提示。
-
-
六、总结与扩展建议
核心要点回顾
-
选择 **
ws** 库:它是Node.js社区中最受欢迎、性能最好的WebSocket实现,API简洁明了。 -
公共封装的价值:通过
WebSocketManager类,我们将连接管理、消息广播等底层逻辑封装起来,使得server/index.js非常干净,只负责启动服务。这大大提高了代码的可维护性和可扩展性。 -
共享端口:将WebSocket服务器挂载到HTTP服务器上,可以让它们共享同一个端口。这在部署时非常方便,也符合很多云服务的要求。
-
数据格式:在实际项目中,建议客户端和服务器之间约定好统一的数据格式(如JSON),并包含
type字段,以便区分不同类型的消息,使逻辑更清晰。
新手扩展建议
这个简易的实现为你打下了坚实的基础。你可以基于此进行更深入的探索:
-
实现私聊:为每个连接的用户分配一个唯一ID(如用户名),在
broadcast方法的基础上,增加一个sendToUser(userId, message)方法,实现点对点通信。 -
集成数据库:将聊天记录、用户信息等持久化到数据库中(如MongoDB、Redis),实现聊天记录的历史查询。
-
增加身份认证:在客户端连接时,通过HTTP请求或在WebSocket握手阶段传递认证信息(如JWT Token),只有认证通过的用户才能连接。
-
部署到线上:学习如何将这个应用部署到云服务器(如阿里云、腾讯云),并配置Nginx作为反向代理,处理HTTPS和WebSocket的wss协议。
WebSocket是构建实时Web应用的基石。希望这篇文章能帮助你快速入门,并掌握良好的封装实践。快去动手试试吧!
如果你有任何问题或想分享你的扩展成果,欢迎在评论区交流~ 关注我,后续分享更多Node.js实战技巧!
🚀🚀 往期项目搭建,可前往
![]()
往期项目搭建,可前往[ Node.js + Koa2 微服务器搭建:初学者也能上手的实战”]👆
![]()
往期项目搭建,可前往[ WebSocket 入门指南:让浏览器和服务器“实时聊天”]👆
![]()
往期项目搭建,可前往[ Vue3 Hooks封装实战:告别重复代码,写出高复用、]👆
![]()
往期项目搭建,可前往[ Vue3路由实战:优雅封装+灵活拦截,解锁路由配置新]👆
![]()
往期项目搭建,可前往[ vue3 + TS + vite 搭建中后台管理系统(完整项目)]👆
![]()
往期项目搭建,可前往[ vue3 + Electron + vite 搭建桌面端应用(完整项目)]👆
![]()
往期项目搭建,可前往[ vue3 + vite + ts + element-ui搭建后台管理框架 ]👆
👆👆点击即可关注 “前端早间课”

总结:
前端路上 | 所知甚少,唯善学。
各位小伙伴有什么疑问,欢迎留言探讨。
— 关注我:前端路上不迷路 —
更多推荐
所有评论(0)