🚀Node.js WebSocket实战:从零搭建实时通信,新手也能懂的封装方案

在Web开发中,实时通信是很多场景的刚需,比如在线聊天、实时数据看板、多人协作编辑等。而WebSocket作为实现双向实时通信的核心技术,让服务器和客户端可以随时主动向对方发送数据,彻底解决了HTTP协议“请求-响应”模式的局限性。

今天就带大家用Node.js从零搭建一个简易的WebSocket服务器,并通过公共封装的方式,让代码结构清晰、易于维护和扩展。全程步骤详细,代码可直接复用,新手也能轻松上手!

📌 核心目标(新手先明确)

  1. 基础搭建:使用 ws 库快速创建一个WebSocket服务器,实现客户端与服务器的连接、消息收发。

  2. 公共封装:将WebSocket的核心逻辑(如连接管理、消息广播、错误处理)封装成独立模块,避免业务代码与底层实现耦合。

  3. 实战落地:实现一个简单的“聊天室”功能,支持新用户上线通知、用户下线通知、群聊消息广播。

  4. 客户端演示:编写一个极简的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>

五、启动与测试

一切准备就绪!现在让我们来启动服务并体验一下。

  1. 启动服务器

在项目根目录下运行:

node server/index.js

如果一切顺利,你会在控制台看到:


✅ WebSocket 管理器已初始化,等待客户端连接...
🚀 HTTP 和 WebSocket 服务已启动,访问 http://localhost:3000
  1. 打开客户端

打开你的浏览器,访问 http://localhost:3000。你会看到一个简单的聊天室界面。

  1. 体验实时通信

    • 单窗口测试:在输入框中输入消息,点击“发送”,你会看到消息显示在消息区域。

    • 多窗口测试(核心):打开另一个浏览器窗口或标签页,同样访问 http://localhost:3000

      • 当第二个窗口打开时,第一个窗口会收到“系统:有新用户加入了聊天室!”的提示。

      • 在任意一个窗口发送消息,两个窗口都会同时收到这条消息,这就是我们实现的广播功能。

      • 关闭其中一个窗口,另一个窗口会收到“系统:有用户离开了聊天室。”的提示。

六、总结与扩展建议

核心要点回顾

  1. 选择 ** ws ** 库:它是Node.js社区中最受欢迎、性能最好的WebSocket实现,API简洁明了。

  2. 公共封装的价值:通过WebSocketManager类,我们将连接管理、消息广播等底层逻辑封装起来,使得server/index.js非常干净,只负责启动服务。这大大提高了代码的可维护性和可扩展性。

  3. 共享端口:将WebSocket服务器挂载到HTTP服务器上,可以让它们共享同一个端口。这在部署时非常方便,也符合很多云服务的要求。

  4. 数据格式:在实际项目中,建议客户端和服务器之间约定好统一的数据格式(如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搭建后台管理框架 ]👆

👆👆点击即可关注 “前端早间课”

在这里插入图片描述

总结:

前端路上 | 所知甚少,唯善学。
各位小伙伴有什么疑问,欢迎留言探讨。

— 关注我:前端路上不迷路 —

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐