Node.js实现WebSocket教程

Node.js实现WebSocket教程

1. WebSocket简介

WebSocket是一种在单个TCP连接上提供全双工通信的协议,允许服务器和客户端之间进行实时、双向通信。本教程将详细讲解如何在Node.js中实现WebSocket。

2. 技术选型

我们将使用ws库来实现WebSocket服务器,并结合express创建Web应用。

2.1 安装依赖

bash 复制代码
# 创建项目目录
mkdir nodejs-websocket-demo
cd nodejs-websocket-demo

# 初始化项目
npm init -y

# 安装依赖
npm install express ws uuid

依赖说明:

  • express: Web应用框架
  • ws: WebSocket服务器实现
  • uuid: 生成唯一标识符

3. 项目结构

复制代码
nodejs-websocket-demo/
│
├── public/
│   ├── index.html
│   └── client.js
├── server.js
└── package.json

4. 详细实现

4.1 WebSocket服务器 (server.js)

javascript 复制代码
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// 存储客户端连接
const clients = new Map();

// 静态文件服务
app.use(express.static('public'));

// WebSocket连接处理
wss.on('connection', (ws) => {
  // 为每个客户端分配唯一ID
  const clientId = uuidv4();
  
  // 存储客户端连接
  clients.set(clientId, ws);

  // 发送客户端ID
  ws.send(JSON.stringify({
    type: 'connection',
    clientId: clientId
  }));

  // 广播新用户连接
  broadcast({
    type: 'userJoined',
    clientId: clientId,
    message: `用户 ${clientId} 已加入聊天`
  }, clientId);

  // 消息处理
  ws.on('message', (message) => {
    try {
      const parsedMessage = JSON.parse(message);
      
      switch(parsedMessage.type) {
        case 'chat':
          handleChatMessage(clientId, parsedMessage);
          break;
        case 'typing':
          handleTypingNotification(clientId, parsedMessage);
          break;
        default:
          console.log('未知消息类型:', parsedMessage.type);
      }
    } catch (error) {
      console.error('消息解析错误:', error);
    }
  });

  // 连接关闭处理
  ws.on('close', () => {
    // 移除客户端
    clients.delete(clientId);

    // 广播用户离开
    broadcast({
      type: 'userLeft',
      clientId: clientId,
      message: `用户 ${clientId} 已离开聊天`
    }, clientId);
  });
});

// 聊天消息处理
function handleChatMessage(senderId, message) {
  broadcast({
    type: 'chat',
    clientId: senderId,
    message: message.message
  }, senderId);
}

// 输入状态通知
function handleTypingNotification(senderId, message) {
  broadcast({
    type: 'typing',
    clientId: senderId,
    isTyping: message.isTyping
  }, senderId);
}

// 广播消息
function broadcast(message, excludeClientId = null) {
  clients.forEach((client, clientId) => {
    if (client.readyState === WebSocket.OPEN && clientId !== excludeClientId) {
      client.send(JSON.stringify(message));
    }
  });
}

// 服务器启动
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`WebSocket服务器运行在 ${PORT} 端口`);
});

4.2 客户端HTML (public/index.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket聊天室</title>
    <style>
        body { 
            font-family: Arial, sans-serif; 
            max-width: 600px; 
            margin: 0 auto; 
            padding: 20px; 
        }
        #chat-messages {
            height: 300px;
            overflow-y: scroll;
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <div id="chat-messages"></div>
    <input type="text" id="message-input" placeholder="输入消息">
    <button id="send-btn">发送</button>

    <script src="client.js"></script>
</body>
</html>

4.3 客户端JavaScript (public/client.js)

javascript 复制代码
const socket = new WebSocket('ws://localhost:3000');
let clientId = null;

const chatMessages = document.getElementById('chat-messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');

// WebSocket事件处理
socket.addEventListener('open', (event) => {
  appendMessage('系统', '连接成功');
});

socket.addEventListener('message', (event) => {
  const message = JSON.parse(event.data);

  switch(message.type) {
    case 'connection':
      clientId = message.clientId;
      appendMessage('系统', `您的ID是: ${clientId}`);
      break;
    case 'chat':
      appendMessage(message.clientId, message.message);
      break;
    case 'userJoined':
    case 'userLeft':
      appendMessage('系统', message.message);
      break;
    case 'typing':
      handleTypingNotification(message);
      break;
  }
});

socket.addEventListener('close', (event) => {
  appendMessage('系统', '连接已关闭');
});

// 发送消息
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    sendMessage();
  }
});

function sendMessage() {
  const message = messageInput.value.trim();
  if (message) {
    socket.send(JSON.stringify({
      type: 'chat',
      message: message
    }));
    appendMessage(clientId, message);
    messageInput.value = '';
  }
}

// 消息追加到聊天窗口
function appendMessage(sender, text) {
  const messageEl = document.createElement('div');
  messageEl.innerHTML = `<strong>${sender}:</strong> ${text}`;
  chatMessages.appendChild(messageEl);
  chatMessages.scrollTop = chatMessages.scrollHeight;
}

// 处理打字状态通知
function handleTypingNotification(message) {
  // 可以在这里实现打字状态提示
}

5. 运行项目

bash 复制代码
# 启动服务器
node server.js

# 访问 http://localhost:3000

6. 功能特点

  1. 实时双向通信
  2. 唯一客户端标识
  3. 广播消息机制
  4. 连接/断开事件处理
  5. 聊天消息和系统通知

7. 性能与扩展建议

  1. 生产环境考虑使用Redis等外部存储管理WebSocket连接
  2. 增加身份验证机制
  3. 实现消息持久化
  4. 使用负载均衡

8. 安全注意事项

  1. 使用WSS(WebSocket Secure)
  2. 实现连接速率限制
  3. 验证和过滤消息内容
  4. 防止跨站WebSocket劫持
相关推荐
gis开发之家9 小时前
vue3直击架构痛点 依赖倒置与接口隔离——打造坚如磐石的插件与高阶组件(HOC)模式
javascript·架构·typescript·前端框架·vue3
柒和远方9 小时前
LeetCode 139. 单词拆分 —— 从暴力回溯到 DP 完全背包
javascript·python·算法
先吃饱再说9 小时前
流式输出完全指南:让 AI 对话像打字机一样流畅
javascript·vue.js
এ慕ོ冬℘゜10 小时前
原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)
开发语言·javascript·ui
mONESY11 小时前
Skill 技能与 MCP 协议的底层逻辑与协同真相
javascript
南川琼语11 小时前
HTTP——AJAX
前端·javascript·ajax
不简说12 小时前
JS 代码技巧 vol.2 — 20 个数组/对象/字符串/正则的实用技巧
前端·javascript·面试
小小编程路13 小时前
C++ 块作用域详解:从基础到实战
前端·javascript·vue.js
虚惊一场13 小时前
MarkView:纯前端 Markdown 实时预览工具的架构设计
前端·javascript·vue.js
冰暮流星13 小时前
javascript之对象的继承
javascript