WebSocket聊天室的简单制作指南

发布于:2025-05-17 ⋅ 阅读:(22) ⋅ 点赞:(0)

一、前言

最近在学习WebSocket技术,做了一个简单的聊天室Demo。这个项目虽然不大,但涵盖了WebSocket的核心功能实现。下面我将详细介绍这个聊天室的实现过程,希望能帮助到同样想学习WebSocket的朋友们。

二、技术选型

  • 后端:Spring Boot + WebSocket

  • 前端:SockJS + STOMP.js

  • 通信协议:STOMP(简单的面向文本的消息协议)

三、后端实现

1. WebSocket配置类

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 设置消息代理前缀
        config.enableSimpleBroker("/topic");
        // 设置应用目的地前缀
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册WebSocket端点
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS(); // 支持SockJS
    }
}

2. 消息处理控制器

@Controller
public class WebSocketController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public String greeting(String message) {
        System.out.println("收到消息: " + message);
        return message;
    }
}

四、前端实现

完整代码:
 

<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
    <title>WebSocket Chat Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <style>
        :root {
            --primary-color: #4CAF50;
            --secondary-color: #45a049;
            --error-color: #f44336;
            --border-color: #ddd;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f9f9f9;
        }

        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 20px;
        }

        #chat-container {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        #message-container {
            height: 400px;
            overflow-y: auto;
            padding: 15px;
            border-bottom: 1px solid var(--border-color);
            background-color: #fafafa;
        }

        .message {
            margin-bottom: 12px;
            padding: 10px 15px;
            border-radius: 18px;
            max-width: 70%;
            word-wrap: break-word;
            animation: fadeIn 0.3s ease;
        }

        .user-message {
            background-color: var(--primary-color);
            color: white;
            margin-left: auto;
            border-bottom-right-radius: 4px;
        }

        .server-message {
            background-color: #e5e5ea;
            color: black;
            margin-right: auto;
            border-bottom-left-radius: 4px;
        }

        #status {
            padding: 10px 15px;
            background-color: #f8f8f8;
            font-size: 14px;
            color: #666;
            text-align: center;
            border-bottom: 1px solid var(--border-color);
        }

        #message-form {
            display: flex;
            padding: 15px;
            background-color: white;
        }

        #message-input {
            flex-grow: 1;
            padding: 12px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            font-size: 16px;
            outline: none;
            transition: border 0.3s;
        }

        #message-input:focus {
            border-color: var(--primary-color);
        }

        #send-btn {
            padding: 12px 20px;
            margin-left: 10px;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        }

        #send-btn:hover {
            background-color: var(--secondary-color);
        }

        #send-btn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(5px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .connection-status {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-right: 5px;
        }

        .connected {
            background-color: var(--primary-color);
        }

        .disconnected {
            background-color: var(--error-color);
        }

        .timestamp {
            font-size: 12px;
            color: #999;
            margin-top: 4px;
            text-align: right;
        }
    </style>
</head>
<body>
<h1>WebSocket Chat Demo</h1>

<div id="chat-container">
    <div id="status">
        <span id="connection-status" class="connection-status disconnected"></span>
        <span id="status-text">正在连接服务器...</span>
    </div>

    <div id="message-container"></div>

    <form id="message-form">
        <input type="text" id="message-input" placeholder="输入消息..." autocomplete="off" autofocus>
        <button type="submit" id="send-btn" disabled>发送</button>
    </form>
</div>

<script>
    // 全局变量
    const socket = new SockJS('http://localhost:8080/ws');
    const stompClient = Stomp.over(socket);
    let username = '用户' + Math.floor(Math.random() * 1000);

    // DOM元素
    const messageContainer = document.getElementById('message-container');
    const messageForm = document.getElementById('message-form');
    const messageInput = document.getElementById('message-input');
    const sendBtn = document.getElementById('send-btn');
    const statusText = document.getElementById('status-text');
    const connectionStatus = document.getElementById('connection-status');

    // 初始化连接
    function connect() {
        updateStatus('正在连接服务器...', 'disconnected');

        // 禁用调试信息(生产环境)
        stompClient.debug = null;

        stompClient.connect({},
            function(frame) {
                onConnectSuccess(frame);
            },
            function(error) {
                onConnectError(error);
            }
        );
    }

    // 连接成功回调
    function onConnectSuccess(frame) {
        updateStatus('已连接', 'connected');
        sendBtn.disabled = false;

        // 订阅消息
        stompClient.subscribe('/topic/greetings', function(response) {
            const message = JSON.parse(response.body);
            // 检查是否是自己的消息
            if (message.username !== username) {
                showMessage(`${message.username}: ${message.content}`, 'server-message');
            }
        });

        // 发送欢迎消息
        sendSystemMessage(`${username} 加入了聊天室`);
    }

    // 连接错误回调
    function onConnectError(error) {
        console.error('连接错误:', error);
        updateStatus('连接失败,5秒后重试...', 'disconnected');
        sendBtn.disabled = true;

        // 5秒后自动重连
        setTimeout(connect, 5000);
    }

    // 更新状态显示
    function updateStatus(text, status) {
        statusText.textContent = text;
        connectionStatus.className = 'connection-status ' + status;
    }

    // 显示消息
    function showMessage(content, messageType) {
        const messageElement = document.createElement('div');
        messageElement.className = `message ${messageType}`;

        const contentElement = document.createElement('div');
        contentElement.textContent = content;

        const timestampElement = document.createElement('div');
        timestampElement.className = 'timestamp';
        timestampElement.textContent = new Date().toLocaleTimeString();

        messageElement.appendChild(contentElement);
        messageElement.appendChild(timestampElement);
        messageContainer.appendChild(messageElement);
        messageContainer.scrollTop = messageContainer.scrollHeight;
    }

    // 发送用户消息
    function sendUserMessage() {
        const content = messageInput.value.trim();
        if (!content) return;

        // 显示用户消息
        showMessage(content, 'user-message');

        // 发送到服务器
        stompClient.send(
            "/app/hello",
            {},
            JSON.stringify({
                username: username,
                content: content,
                timestamp: new Date().getTime()
            })
        );

        messageInput.value = '';
    }

    // 发送系统消息
    function sendSystemMessage(message) {
        showMessage(message, 'server-message');
    }

    // 事件监听
    messageForm.addEventListener('submit', function(e) {
        e.preventDefault();
        sendUserMessage();
    });

    // 回车发送消息
    messageInput.addEventListener('keypress', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            sendUserMessage();
        }
    });

    // 初始化
    connect();

    // 窗口关闭前发送离开消息
    window.addEventListener('beforeunload', function() {
        if (stompClient.connected) {
            sendSystemMessage(`${username} 离开了聊天室`);
        }
    });
</script>
</body>
</html>

JavaScript核心代码

// 初始化连接
const socket = new SockJS('http://localhost:8080/ws');
const stompClient = Stomp.over(socket);

// 连接成功回调
function onConnectSuccess(frame) {
    updateStatus('已连接', 'connected');
    sendBtn.disabled = false;

    // 订阅消息
    stompClient.subscribe('/topic/greetings', function(response) {
        const message = JSON.parse(response.body);
        // 检查是否是自己的消息
        if (message.username !== username) {
            showMessage(`${message.username}: ${message.content}`, 'server-message');
        }
    });
}

// 发送消息
function sendUserMessage() {
    const content = messageInput.value.trim();
    if (!content) return;

    // 显示用户消息
    showMessage(content, 'user-message');

    // 发送到服务器
    stompClient.send(
        "/app/hello",
        {},
        JSON.stringify({
            username: username,
            content: content,
            timestamp: new Date().getTime()
        })
    );
}

五、遇到的问题及解决方案

问题:消息重复显示

现象:自己发送的消息会显示两次

原因

  1. 前端发送后立即显示

  2. 服务器广播后又显示一次

解决方案

stompClient.subscribe('/topic/greetings', function(response) {
    const message = JSON.parse(response.body);
    // 只显示别人发的消息
    if (message.username !== username) {
        showMessage(`${message.username}: ${message.content}`, 'server-message');
    }
});

六、项目特点

  1. 简单易用:代码简洁,适合初学者理解WebSocket原理

  2. 实时通信:基于WebSocket实现真正的双向通信

  3. 自动重连:网络断开后会自动尝试重新连接

  4. 用户区分:每个用户有随机生成的唯一用户名

七、总结

这个简单的WebSocket聊天室Demo虽然功能不多,但涵盖了WebSocket的核心功能。通过这个项目,我学到了:

  1. WebSocket的基本工作原理

  2. STOMP协议的使用方法

  3. 前后端如何通过WebSocket进行实时通信

  4. 如何处理常见的消息回显问题

如果想扩展功能,可以考虑添加:

  • 用户列表显示

  • 私聊功能

  • 消息历史记录

  • 图片/文件传输


未来计划:

Ai 聊天室   --- 未来可期


网站公告

今日签到

点亮在社区的每一天
去签到