杰瑞科技汇

Java WebSocket聊天室如何实现实时消息推送?

最终效果

我们将实现一个简单的聊天室,具有以下功能:

Java WebSocket聊天室如何实现实时消息推送?-图1
(图片来源网络,侵删)
  1. 用户进入聊天室,会收到欢迎消息,并通知其他用户。
  2. 用户发送消息,所有在线用户都能实时看到。
  3. 用户离开聊天室,会收到离开消息,并通知其他用户。

第一步:项目环境准备

创建 Maven Web 项目

在你的 IDE(如 IntelliJ IDEA 或 Eclipse)中,创建一个新的 Maven Web 项目。

添加 Maven 依赖

我们需要添加 WebSocket API 的依赖,对于 Java 11+,我们使用 jakarta.platform

打开 pom.xml 文件,添加以下依赖:

<dependencies>
    <!-- Jakarta WebSocket API -->
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>9.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!-- 为了方便测试,我们添加一个简单的嵌入式服务器 (可选) -->
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>11.0.15</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>11.0.15</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-webapp</artifactId>
        <version>11.0.15</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty.websocket</groupId>
        <artifactId>websocket-jakarta-server</artifactId>
        <version>11.0.15</version>
        <scope>test</scope>
    </dependency>
</dependencies>

注意: <scope>provided</scope> 表示这些依赖在部署到像 Tomcat 或 WildFly 这样的应用服务器时会由服务器提供,因此我们不需要在打包的 WAR 文件中包含它们。

Java WebSocket聊天室如何实现实时消息推送?-图2
(图片来源网络,侵删)

配置 Web 部署描述符 (web.xml)

src/main/webapp/WEB-INF/ 目录下,创建 web.xml 文件,虽然对于简单的 WebSocket 应用它不是必须的,但保持一个空的 web.xml 是一个好习惯。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
</web-app>

第二步:创建 WebSocket 端点

这是聊天室的核心,它负责处理所有的 WebSocket 连接、消息和断开事件。

src/main/java 下创建你的包,com.example.chat,然后创建 ChatServerEndpoint.java 文件。

package com.example.chat;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
 * WebSocket 聊天室端点
 * @ServerEndpoint 注解将一个类定义为 WebSocket 端点,
 * value 属性是 WebSocket 的连接 URL。
 */
@ServerEndpoint("/chat")
public class ChatServerEndpoint {
    // 使用一个静态的 Set 来存储所有连接的会话。
    // 必须是线程安全的,因为多个线程(来自不同客户端)可能会同时访问它。
    // 使用 Collections.synchronizedSet 包装 HashSet 来保证线程安全。
    private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
    /**
     * 当一个新的 WebSocket 连接建立时被调用。
     * @param session 与客户端关联的会话
     */
    @OnOpen
    public void onOpen(Session session) {
        // 将新会话添加到集合中
        chatroomUsers.add(session);
        System.out.println("新连接: " + session.getId() + " 已加入聊天室,当前在线人数: " + chatroomUsers.size());
        // 通知其他用户
        broadcastMessage("系统消息: 欢迎 " + session.getId() + " 加入聊天室!");
    }
    /**
     * 当客户端发送消息时被调用。
     * @param message 客户端发送的消息
     * @param session 发送消息的会话
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自 " + session.getId() + " 的消息: " + message);
        // 将消息广播给所有用户
        broadcastMessage(session.getId() + ": " + message);
    }
    /**
     * 当 WebSocket 连接关闭时被调用。
     * @param session 关闭的会话
     * @param closeReason 关闭原因
     */
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        // 从集合中移除断开连接的会话
        chatroomUsers.remove(session);
        System.out.println("连接关闭: " + session.getId() + " 已离开聊天室,原因: " + closeReason.getReasonPhrase());
        System.out.println("当前在线人数: " + chatroomUsers.size());
        // 通知其他用户
        broadcastMessage("系统消息: " + session.getId() + " 已离开聊天室。");
    }
    /**
     * 当 WebSocket 连接过程中发生错误时被调用。
     * @param session 出错的会话
     * @param throwable 捕获到的异常
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        System.err.println("会话 " + session.getId() + " 发生错误:");
        throwable.printStackTrace();
        // 发生错误时,也将其从用户列表中移除
        chatroomUsers.remove(session);
    }
    /**
     * 广播消息给所有连接的客户端。
     * @param message 要广播的消息
     */
    private void broadcastMessage(String message) {
        // 同步遍历,以防止在迭代过程中集合被修改
        synchronized (chatroomUsers) {
            for (Session user : chatroomUsers) {
                try {
                    // 如果会话是打开的,则发送消息
                    if (user.isOpen()) {
                        user.getBasicRemote().sendText(message);
                    }
                } catch (IOException e) {
                    System.err.println("向用户 " + user.getId() + " 发送消息失败: " + e.getMessage());
                    // 如果发送失败,可能是客户端已经断开,可以将其从集合中移除
                    chatroomUsers.remove(user);
                }
            }
        }
    }
}

第三步:创建前端页面

我们需要一个 HTML 页面来连接 WebSocket 并显示聊天内容。

Java WebSocket聊天室如何实现实时消息推送?-图3
(图片来源网络,侵删)

src/main/webapp 目录下创建 index.html 文件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">Java WebSocket 聊天室</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #chat-log {
            border: 1px solid #ccc;
            padding: 10px;
            height: 400px;
            overflow-y: scroll;
            margin-bottom: 10px;
            background-color: #f9f9f9;
        }
        #message-input {
            padding: 8px;
            width: 70%;
        }
        #send-button {
            padding: 8px 15px;
            cursor: pointer;
        }
        .system-message {
            color: #888;
            font-style: italic;
        }
    </style>
</head>
<body>
    <h1>Java WebSocket 聊天室</h1>
    <div id="chat-log"></div>
    <div>
        <input type="text" id="message-input" placeholder="输入消息..." onkeydown="if(event.key==='Enter') sendMessage()">
        <button id="send-button" onclick="sendMessage()">发送</button>
    </div>
    <script>
        // 1. 创建 WebSocket 连接
        // 注意:这里的 URL 必须与 @ServerEndpoint 的 value 属性匹配
        // 使用 wss:// 表示安全连接 (WebSocket Secure),如果是 http 则用 ws://
        // 在本地开发时,浏览器会自动将 http 升级为 ws。
        const socket = new WebSocket('ws://' + window.location.host + '/your-app-name/chat');
        const chatLog = document.getElementById('chat-log');
        const messageInput = document.getElementById('message-input');
        // 2. 监听 WebSocket 事件
        // 连接打开事件
        socket.onopen = function (event) {
            console.log("WebSocket 连接已建立。");
            appendMessage("系统消息: 你已成功连接到聊天室。", true);
        };
        // 接收消息事件
        socket.onmessage = function (event) {
            console.log("收到消息: " + event.data);
            appendMessage(event.data);
        };
        // 连接关闭事件
        socket.onclose = function (event) {
            if (event.wasClean) {
                console.log(`连接已正常关闭,代码=${event.code} 原因=${event.reason}`);
            } else {
                console.error('连接被意外中断');
            }
            appendMessage("系统消息: 与服务器的连接已断开。", true);
        };
        // 发生错误事件
        socket.onerror = function (error) {
            console.error("WebSocket 错误: " + error);
            appendMessage("系统消息: 发生错误,请检查控制台。", true);
        };
        // 3. 发送消息的函数
        function sendMessage() {
            const message = messageInput.value.trim();
            if (message) {
                // 通过 WebSocket 发送消息
                socket.send(message);
                messageInput.value = ''; // 清空输入框
            }
        }
        // 4. 将消息添加到聊天日志的函数
        function appendMessage(message, isSystem = false) {
            const messageElement = document.createElement('div');
            messageElement.textContent = message;
            if (isSystem) {
                messageElement.className = 'system-message';
            }
            chatLog.appendChild(messageElement);
            // 滚动到底部
            chatLog.scrollTop = chatLog.scrollHeight;
        }
    </script>
</body>
</html>

重要提示:在 new WebSocket(...) 的 URL 中,/your-app-name/ 需要替换成你实际部署的 Web 应用的上下文路径,如果你在本地直接用 IDE 运行,通常就是项目名。


第四步:部署和运行

你有两种主要的方式来运行这个应用:

使用 IDE(推荐,如 IntelliJ IDEA)

  1. 配置 Tomcat/WildFly:

    • 在 IntelliJ IDEA 中,打开 Run/Debug Configurations
    • 点击 号,选择 Tomcat Server -> Local
    • 为你的服务器命名(如 "WebSocket Chat")。
    • Deployment 标签页,点击 ,选择 Artifact,然后选择你刚刚创建的 Web 项目(通常是 your-app-name:war exploded)。
    • 应用并保存配置。
  2. 运行:

    • 点击右上角的绿色 "Run" 按钮。
    • IDE 会启动 Tomcat 服务器,并将你的应用部署上去。
    • 浏览器会自动打开 http://localhost:8080/your-app-name/index.html
    • 打开两个或更多浏览器窗口,你就可以看到聊天效果了!

使用 Maven 命令行和嵌入式 Jetty

如果你在 pom.xml 中添加了 Jetty 依赖,你可以创建一个简单的 Java 类来启动服务器。

  1. 创建一个主类 com.example.chat.MainApp.java:
package com.example.chat;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MainApp {
    public static void main(String[] args) throws Exception {
        // 创建 Jetty 服务器,监听 8080 端口
        Server server = new Server(8080);
        // 创建 Servlet 上下文处理器
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/"); // 设置根上下文
        server.setHandler(context);
        // 注册 WebSocket 端点
        // 这个步骤告诉 Jetty 如何处理 /chat 路径上的 WebSocket 连接
        JakartaWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> {
            // 添加你的 WebSocket 端点
            wsContainer.addEndpoint(ChatServerEndpoint.class);
        });
        // 为根路径 "/" 提供静态文件服务
        context.addServlet(new ServletHolder(new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
                if (req.getRequestURI().equals("/") || req.getRequestURI().equals("/index.html")) {
                    resp.setContentType("text/html");
                    resp.getWriter().write("""
                        <!DOCTYPE html>
                        <html>
                        <head>
                            <title>Redirect</title>
                            <meta http-equiv="refresh" content="0; url='/index.html'"/>
                        </head>
                        <body>
                            <p>Redirecting to chat room...</p>
                        </body>
                        </html>
                        """);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
                }
            }
        }), "/*");
        System.out.println("Starting server on http://localhost:8080");
        server.start();
        server.join();
    }
}
  1. 运行:
    • 在 IDE 中直接运行 MainAppmain 方法。
    • 然后在浏览器中访问 http://localhost:8080

总结与扩展

恭喜!你已经成功创建了一个基于 Java WebSocket 的聊天室。

核心要点回顾:

  1. @ServerEndpoint: 标识这是一个 WebSocket 端点。
  2. @OnOpen, @OnMessage, @OnClose, @OnError: 这些注解定义了连接生命周期中的关键事件处理方法。
  3. Session 对象: 代表与单个客户端的连接,可以用来发送消息(session.getBasicRemote().sendText(...))。
  4. 广播机制: 通过一个静态的 Set<Session> 来管理所有连接,实现消息广播。
  5. 前端 WebSocket API: 使用 JavaScript 的 WebSocket 对象建立连接、收发消息。

可以扩展的功能:

  • 用户名: 让用户输入用户名,而不是使用 session.getId()
  • 私聊: 实现用户对用户的私聊功能。
  • 在线用户列表: 在页面上显示当前所有在线的用户。
  • 消息持久化: 将聊天记录保存到数据库。
  • 表情包和文件传输: 扩展消息格式,支持富媒体内容。
  • 安全性: 添加身份验证,确保只有登录用户才能进入聊天室。
分享:
扫描分享到社交APP
上一篇
下一篇