杰瑞科技汇

java websocket 聊天

  1. 核心概念简介:WebSocket 是什么,为什么用它。
  2. 项目搭建:使用 Spring Initializr 创建项目。
  3. 后端实现:创建 WebSocket 端点、处理消息、广播消息。
  4. 前端实现:创建聊天界面,连接 WebSocket,发送和接收消息。
  5. 测试与运行:启动应用,体验聊天功能。

核心概念简介

HTTP vs. WebSocket

java websocket 聊天-图1
(图片来源网络,侵删)
  • HTTP (请求-响应模式):客户端必须先发起请求,服务器才能响应,服务器无法主动向客户端推送信息,这对于需要实时更新的应用(如聊天室、股票行情)效率很低,通常需要轮询(Polling)或长轮询(Long Polling),这会造成不必要的网络开销和延迟。
  • WebSocket (全双工通信):它是一个在单个 TCP 连接上进行全双工通信的协议,一旦连接建立,客户端和服务器就可以随时相互发送数据,无需一方先发起请求,这为实时应用提供了高效、低延迟的解决方案。

Spring WebSocket 架构

Spring 对 WebSocket 提供了非常优雅的支持,其核心组件包括:

  • @ServerEndpoint (标准 Java API):如果使用标准 Java WebSocket API,可以用这个注解来定义一个 WebSocket 端点。
  • WebSocketMessageBroker (Spring 方式):这是 Spring 推荐的方式,它基于消息代理,更加灵活和强大,它引入了“消息代理”(Message Broker)的概念,服务器和客户端都通过消息代理来收发消息,而不是直接点对点,这使得构建如聊天室(广播)、私信(点对点)等场景变得更加简单。

在本教程中,我们将使用更简单、更直接的 @ServerEndpoint 方式,因为它更容易理解 WebSocket 的基本工作原理。


项目搭建

  1. 访问 Spring Initializr
  2. 填写项目信息:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 选择一个较新的稳定版本 (如 3.x.x)
    • Project Metadata:
      • Group: com.example
      • Artifact: websocket-chat
      • Name: websocket-chat
      • Description: Demo project for WebSocket Chat
      • Package name: com.example.websocketchat
    • Dependencies: 添加以下依赖
      • Spring Web: 用于提供 Web 服务。
      • WebSocket: 这是核心依赖,提供了 WebSocket 支持。
  3. 点击 "GENERATE" 下载项目压缩包,并用你的 IDE (如 IntelliJ IDEA 或 Eclipse) 打开。

下载后,你的 pom.xml 文件会自动包含 spring-boot-starter-websocket 依赖。

java websocket 聊天-图2
(图片来源网络,侵删)

后端实现

我们将创建一个 WebSocket 端点来处理连接、断开连接、接收和发送消息。

1 创建 WebSocket 端点类

src/main/java/com/example/websocketchat 目录下创建一个新类 ChatEndpoint.java

package com.example.websocketchat;
import jakarta.websocket.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@Component // 使用 @Component 让 Spring 管理
@ServerEndpoint("/chat") // 定义 WebSocket 端点路径
public class ChatEndpoint {
    // 使用 Set 来存储所有连接的会话,实现广播功能
    // 使用 Collections.synchronizedSet 保证线程安全
    private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
    private static final Logger logger = LoggerFactory.getLogger(ChatEndpoint.class);
    // 当一个新的 WebSocket 连接建立时被调用
    @OnOpen
    public void onOpen(Session session) {
        chatroomUsers.add(session);
        logger.info("新用户连接: {}, 当前在线人数: {}", session.getId(), chatroomUsers.size());
        // 广播新用户加入的消息
        broadcastMessage("系统: 用户 " + session.getId() + " 加入了聊天室。");
    }
    // 当客户端发送消息时被调用
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("来自用户 {} 的消息: {}", session.getId(), message);
        // 广播消息给所有用户
        broadcastMessage("用户 " + session.getId() + ": " + message);
    }
    // 当 WebSocket 连接关闭时被调用
    @OnClose
    public void onClose(Session session) {
        chatroomUsers.remove(session);
        logger.info("用户断开连接: {}, 当前在线人数: {}", session.getId(), chatroomUsers.size());
        // 广播用户离开的消息
        broadcastMessage("系统: 用户 " + session.getId() + " 离开了聊天室。");
    }
    // 当 WebSocket 连接发生错误时被调用
    @OnError
    public void onError(Throwable error, Session session) {
        logger.error("用户 {} 发生错误: {}", session.getId(), error.getMessage());
        chatroomUsers.remove(session);
    }
    // 广播消息给所有连接的客户端
    private void broadcastMessage(String message) {
        // 同步遍历,防止在遍历时集合被修改
        synchronized (chatroomUsers) {
            for (Session session : chatroomUsers) {
                if (session.isOpen()) {
                    try {
                        // getBasicRemote() 是同步的,适合简单场景
                        session.getBasicRemote().sendText(message);
                    } catch (IOException e) {
                        logger.error("向用户 {} 发送消息失败", session.getId(), e);
                        // 如果发送失败,可能连接已断开,从集合中移除
                        chatroomUsers.remove(session);
                    }
                }
            }
        }
    }
}

代码解释:

  • @Component: 告诉 Spring 这是一个 Spring Bean,需要被管理。
  • @ServerEndpoint("/chat"): 将这个类声明为一个 WebSocket 端点,客户端可以通过 ws://your-host:your-port/chat 来连接。
  • @OnOpen, @OnMessage, @OnClose, @OnError: 这些是生命周期方法注解,分别对应连接建立、收到消息、连接关闭和发生错误。
  • Set<Session> chatroomUsers: 这是一个静态的、线程安全的集合,用来保存所有当前连接的客户端会话,这是实现聊天室广播功能的关键。
  • broadcastMessage(String message): 这个方法遍历所有连接的会话,并向每个会话发送消息。

2 启用 WebSocket 支持

为了让 Spring Boot 应用识别并启用我们的 WebSocket 端点,我们需要创建一个配置类。

java websocket 聊天-图3
(图片来源网络,侵删)

src/main/java/com/example/websocketchat 目录下创建 WebSocketConfig.java

package com.example.websocketchat;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket // 启用 WebSocket 功能
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册我们自定义的 ChatEndpoint
        // .setAllowedOrigins("*") 允许所有来源的连接,适合开发。
        // 在生产环境中,应该指定具体的域名,如 "http://localhost:8080"
        registry.addHandler(new ChatEndpoint(), "/chat")
                .setAllowedOrigins("*");
    }
}

代码解释:

  • @Configuration: 表明这是一个配置类。
  • @EnableWebSocket: 启用 Spring WebSocket 功能。
  • WebSocketConfigurer: 实现这个接口来配置 WebSocket。
  • registerWebSocketHandlers: 在这个方法中,我们将 ChatEndpoint 注册到 /chat 路径上。setAllowedOrigins("*") 是为了允许前端页面(可能运行在不同的端口或域名上)连接到后端 WebSocket。

前端实现

现在我们来创建一个简单的 HTML 页面作为聊天客户端。

src/main/resources/static 目录下创建一个 index.html 文件。static 目录不存在,请手动创建。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">WebSocket 聊天室</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #chat-box {
            border: 1px solid #ccc;
            padding: 10px;
            height: 400px;
            overflow-y: scroll;
            margin-bottom: 10px;
            background-color: #f9f9f9;
        }
        #message-input {
            width: 70%;
            padding: 8px;
        }
        #send-button {
            width: 25%;
            padding: 8px;
            background-color: #007bff;
            color: white;
            border: none;
            cursor: pointer;
        }
        #send-button:hover {
            background-color: #0056b3;
        }
        .message {
            margin-bottom: 5px;
            padding: 5px;
            border-radius: 5px;
        }
        .system-message {
            color: #888;
            font-style: italic;
        }
        .user-message {
            color: #333;
        }
    </style>
</head>
<body>
    <h2>WebSocket 聊天室</h2>
    <div id="chat-box"></div>
    <div>
        <input type="text" id="message-input" placeholder="输入消息...">
        <button id="send-button">发送</button>
    </div>
    <script>
        // 1. 获取 DOM 元素
        const chatBox = document.getElementById('chat-box');
        const messageInput = document.getElementById('message-input');
        const sendButton = document.getElementById('send-button');
        // 2. 创建 WebSocket 连接
        // 注意:这里的 URL 必须和后端 @ServerEndpoint 的路径一致
        // 使用 ws:// (非加密) 或 wss:// (加密)
        const ws = new WebSocket("ws://localhost:8080/chat");
        // 3. 监听 WebSocket 事件
        // 连接成功时触发
        ws.onopen = function(event) {
            console.log("WebSocket 连接成功!");
            appendMessage("系统: 你已成功连接到聊天室。", "system-message");
        };
        // 收到消息时触发
        ws.onmessage = function(event) {
            console.log("收到消息: " + event.data);
            appendMessage(event.data, "user-message");
        };
        // 连接关闭时触发
        ws.onclose = function(event) {
            console.log("WebSocket 连接已关闭。");
            appendMessage("系统: 你与聊天室的连接已断开。", "system-message");
        };
        // 发生错误时触发
        ws.onerror = function(error) {
            console.error("WebSocket 错误: " + error);
            appendMessage("系统: 发生连接错误。", "system-message");
        };
        // 4. 发送消息
        function sendMessage() {
            const message = messageInput.value.trim();
            if (message) {
                ws.send(message); // 通过 WebSocket 发送消息
                messageInput.value = ""; // 清空输入框
            }
        }
        // 绑定发送按钮的点击事件
        sendButton.onclick = sendMessage;
        // 绑定输入框的回车事件
        messageInput.onkeypress = function(event) {
            if (event.key === "Enter") {
                sendMessage();
            }
        };
        // 5. 将消息添加到聊天框
        function appendMessage(message, className) {
            const messageElement = document.createElement("div");
            messageElement.classList.add("message", className);
            messageElement.textContent = message;
            chatBox.appendChild(messageElement);
            // 滚动到底部
            chatBox.scrollTop = chatBox.scrollHeight;
        }
    </script>
</body>
</html>

代码解释:

  • new WebSocket("ws://localhost:8080/chat"): 创建一个 WebSocket 连接,URL 指向后端定义的端点。
  • ws.onopen, ws.onmessage, ws.onclose, ws.onerror: JavaScript 的 WebSocket API 提供的事件监听器,分别对应连接生命周期。
  • ws.send(message): 向服务器发送消息。
  • appendMessage: 一个辅助函数,用于将接收到的消息格式化并显示在页面上。

测试与运行

  1. 运行后端:

    • 在你的 IDE 中,找到 WebSocketChatApplication.java (这是 Spring Boot 的主启动类)。
    • 右键点击 Run,应用将在 8080 端口启动。
  2. 测试聊天:

    • 打开浏览器 (如 Chrome),访问 http://localhost:8080,由于我们放在了 static 目录下,Spring Boot 会自动提供 index.html
    • 打开第二个浏览器窗口或标签页,同样访问 http://localhost:8080
    • 在任意一个窗口中输入消息并点击“发送”或按回车。
    • 你会看到消息立即显示在两个窗口的聊天框中。
    • 当你关闭一个窗口时,另一个窗口会收到“用户离开”的系统消息。

总结与进阶

你已经成功构建了一个基础的 Java WebSocket 聊天应用!

这个例子的关键点:

  • 后端: 使用 @ServerEndpoint 定义端点,用 Set<Session> 管理所有连接,并通过 session.getBasicRemote().sendText() 实现广播。
  • 前端: 使用 new WebSocket() 创建连接,并通过 onmessage 事件监听服务器推送的消息。

可以进一步改进的方向:

  1. 用户身份认证: 当前用户只是一个 session.getId(),你可以结合 Spring Security,在 @OnOpen 时从 HTTP Session 或 Token 中获取用户名,并在消息中显示。
  2. 消息持久化: 将聊天记录保存到数据库(如 MySQL, PostgreSQL)中,这样用户刷新页面后还能看到历史记录。
  3. 私聊功能: 修改 broadcastMessage 方法,增加一个 toUserId 参数,只将消息发送给指定的用户。
  4. 使用 STOMP 协议: 对于更复杂的场景(如需要消息队列、订阅/发布模式),可以使用 Spring 的 STOMP (Simple Text Oriented Messaging Protocol) over WebSocket,它提供了消息路由功能,比原始的 WebSocket 更强大。
  5. 前端框架集成: 将聊天功能集成到 Vue, React 或 Angular 等现代前端框架中,构建更复杂的用户界面。
分享:
扫描分享到社交APP
上一篇
下一篇