前言

这篇文章将带你从 0 开始,一步步实现一个能多人实时聊天的系统,包括:

  • 前端:用 React 构建聊天界面,实时发送接收消息
  • 后端:用 Node.js 搭建 WebSocket 服务,处理多人通信

整体架构预览

浏览器(前端)
 └─ React App(WebSocket 客户端)📨
        │
        ▼
WebSocket 连接(ws://localhost:3001)
        │
        ▼
Node.js(服务端)
 └─ ws 库(WebSocket 服务器)

技术选型

  • 前端:React + TypeScript + Tailwind CSS
  • 后端:Node.js + ws(轻量级 WebSocket 库)
  • 网络协议:WebSocket(基于 TCP 的双向通信协议)

一、前端:React 聊天室实现(含消息收发 + 自动重连)

src/
├── App.tsx             // 登录用户名
├── ChatRoom.tsx        // 聊天主界面
├── useWebSocket.ts     // 封装连接逻辑
└── index.css           // Tailwind 样式导入

1.1 useWebSocket.ts:封装 WebSocket 逻辑

目标:封装一个可复用的 Hook,管理连接、接收消息、心跳机制、重连等。

// src/useWebSocket.ts
import { useEffect, useRef, useState } from "react";

// 封装 WebSocket 的 Hook
export function useWebSocket(url: string, username: string) {
  const ws = useRef<WebSocket | null>(null);      // WebSocket 实例
  const heartbeat = useRef<any>(null);            // 心跳定时器
  const reconnectTimer = useRef<any>(null);       // 自动重连定时器

  const [status, setStatus] = useState<"connecting" | "open" | "closed">("connecting");
  const [messages, setMessages] = useState<{ sender: string; text: string }[]>([]);

  // 创建连接的函数(可重复调用)
  const connect = () => {
    ws.current = new WebSocket(url); // 建立连接

    // 连接成功
    ws.current.onopen = () => {
      setStatus("open");
      // 告诉服务器“我加入了聊天室”
      ws.current?.send(JSON.stringify({ type: "join", sender: username }));

      // 每 30 秒发一个“ping”保持连接
      heartbeat.current = setInterval(() => {
        ws.current?.send(JSON.stringify({ type: "ping" }));
      }, 30000);
    };

    // 收到服务器消息时触发
    ws.current.onmessage = (e) => {
      try {
        const data = JSON.parse(e.data);
        // 如果是聊天消息,加入到消息列表
        if (data.type === "chat") {
          setMessages((prev) => [...prev, { sender: data.sender, text: data.text }]);
        }
      } catch {}
    };

    // 连接断开(包括关闭和异常)
    ws.current.onclose = () => {
      setStatus("closed");
      clearInterval(heartbeat.current);
      // 3 秒后尝试重新连接
      reconnectTimer.current = setTimeout(connect, 3000);
    };

    ws.current.onerror = () => {
      setStatus("closed");
    };
  };

  // 提供一个发送消息的函数
  const sendMessage = (text: string) => {
    if (ws.current?.readyState === WebSocket.OPEN) {
      ws.current.send(JSON.stringify({ type: "chat", sender: username, text }));
    }
  };

  // 页面加载时连接;页面关闭时断开
  useEffect(() => {
    connect();

    return () => {
      clearInterval(heartbeat.current);
      clearTimeout(reconnectTimer.current);
      ws.current?.close();
    };
  }, [url]);

  return { status, messages, sendMessage };
}

1.2 ChatRoom.tsx:聊天室界面组件

功能:展示消息、输入内容、发送消息、显示连接状态

// src/ChatRoom.tsx
import { useState } from "react";
import { useWebSocket } from "./useWebSocket";

export default function ChatRoom({ username }: { username: string }) {
  const { status, messages, sendMessage } = useWebSocket("ws://localhost:3001", username);
  const [input, setInput] = useState("");

  return (
    <div className="max-w-md mx-auto mt-8 p-4 border rounded bg-white shadow">
      <h2 className="text-xl font-bold mb-4">👋 欢迎,{username}</h2>

      {/* 显示当前连接状态 */}
      <div className="text-sm text-gray-500 mb-2">
        状态:{status === "open" ? "✅ 已连接" : "⏳ 连接中/断开"}
      </div>

      {/* 消息展示区域 */}
      <div className="h-64 overflow-auto bg-gray-50 border p-2 mb-2 rounded">
        {messages.map((msg, idx) => (
          <div key={idx} className="mb-1">
            <strong>{msg.sender}</strong>: {msg.text}
          </div>
        ))}
      </div>

      {/* 输入框 + 发送按钮 */}
      <div className="flex gap-2">
        <input
          className="flex-1 border px-2 py-1 rounded"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="说点什么吧"
        />
        <button
          className="bg-blue-600 text-white px-4 py-1 rounded"
          onClick={() => {
            if (input.trim()) {
              sendMessage(input);
              setInput(""); // 清空输入框
            }
          }}
        >
          发送
        </button>
      </div>
    </div>
  );
}

1.3 App.tsx:用户名登录页

// src/App.tsx
import { useState } from "react";
import ChatRoom from "./ChatRoom";

export default function App() {
  const [username, setUsername] = useState("");
  const [joined, setJoined] = useState(false);

  // 未登录则显示输入框
  if (!joined) {
    return (
      <div className="flex flex-col items-center mt-32 gap-4">
        <h1 className="text-2xl font-bold">🔐 请输入用户名进入聊天室</h1>
        <input
          className="border rounded px-3 py-2"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <button
          className="bg-green-600 text-white px-4 py-2 rounded"
          onClick={() => username.trim() && setJoined(true)}
        >
          进入聊天室
        </button>
      </div>
    );
  }

  return <ChatRoom username={username} />;
}

1.4 启动前端项目

1.4.1 创建项目

npm create vite@latest websocket-chat --template react-ts
cd websocket-chat
npm install

1.4.2 安装 Tailwind

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

1.4.3 配置 tailwind.config.js

content: ["./index.html", "./src/**/*.{ts,tsx}"],

1.4.4 配置 tailwind.config.js

@tailwind base;
@tailwind components;
@tailwind utilities;

1.4.5 配置 tailwind.config.js

npm run dev

二、后端:Node.js + ws 实现 WebSocket 服务端

2.1 初始化项目

mkdir websocket-server
cd websocket-server
npm init -y
npm install ws

2.3 server.js:服务端实现代码(详细注释)

// server.js
const WebSocket = require("ws");

// 创建一个 WebSocket 服务器,监听 3001 端口
const wss = new WebSocket.Server({ port: 3001 });
console.log("✅ WebSocket 服务已启动:ws://localhost:3001");

const clients = new Set(); // 存储所有连接的客户端

// 新客户端连接时
wss.on("connection", (ws) => {
  console.log("🔌 有客户端连接");
  clients.add(ws);
  ws.isAlive = true;

  // 客户端发来消息
  ws.on("message", (msg) => {
    try {
      const data = JSON.parse(msg);

      if (data.type === "join") {
        // 广播“某某加入了聊天室”
        broadcast({ type: "chat", sender: "系统", text: `${data.sender} 加入了聊天室` });
      }

      if (data.type === "chat") {
        // 聊天内容广播给所有人
        broadcast({ type: "chat", sender: data.sender, text: data.text });
      }

      if (data.type === "ping") {
        ws.isAlive = true; // 用于心跳
      }
    } catch (err) {
      console.error("❌ 无效消息", msg.toString());
    }
  });

  // 客户端断开连接
  ws.on("close", () => {
    clients.delete(ws);
    console.log("📴 有客户端断开连接");
  });
});

// 心跳检测(每 30 秒检查是否仍连接)
setInterval(() => {
  for (let ws of clients) {
    if (!ws.isAlive) {
      console.log("⚠️ 心跳失败,断开连接");
      ws.terminate(); // 强制断开连接
      clients.delete(ws);
      continue;
    }

    ws.isAlive = false;
    ws.ping(); // 向客户端发送 ping
  }
}, 30000);

// 广播消息给所有客户端
function broadcast(data) {
  const msg = JSON.stringify(data);
  for (let client of clients) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(msg);
    }
  }
}

2.4 启动后端服务

node server.js

会看到

✅ WebSocket 服务已启动:ws://localhost:3001

2.5 最终效果展示

  • 多人打开前端页面输入不同用户名
  • 任意一个人发言,所有人即时收到
  • 自动断线重连
  • 服务端日志打印连接/断开/心跳等信息
Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐