杰瑞科技汇

Java实现WebSocket聊天室,如何实现?

  1. 环境准备:创建一个 Maven Web 项目。
  2. 后端实现:编写 WebSocket 端点和消息处理逻辑。
  3. 前端实现:创建一个简单的 HTML 页面用于发送和接收消息。
  4. 部署与测试:将应用打包成 WAR 文件并部署到 Tomcat 服务器上进行测试。

第 1 步:环境准备 (Maven Web 项目)

你需要一个 Java Web 开发环境,并确保你有一个支持 WebSocket 的 Servlet 容器,Apache Tomcat 8+Jetty 9+

Java实现WebSocket聊天室,如何实现?-图1
(图片来源网络,侵删)

使用你的 IDE(如 IntelliJ IDEA 或 Eclipse)创建一个新的 Maven Web App 项目,你的 pom.xml 文件需要包含 javax WebSocket API 的依赖,如果你使用的是较新的 Tomcat 版本(如 10+),依赖包名会从 javax.websocket 变为 jakarta.websocket

这里我们以 Tomcat 9 为例,使用 javax 包。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>java-websocket-chatroom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- WebSocket API -->
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope> <!-- 由 Tomcat 这样的容器提供 -->
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>

第 2 步:后端实现

WebSocket 的核心是一个服务器端的“端点”(Endpoint),我们将创建一个 ChatServerEndpoint 类来处理所有 WebSocket 连接、消息和断开事件。

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

创建 WebSocket 端点类

src/main/java 下创建你的包,com.example.websocket,然后创建 ChatServerEndpoint.java

ChatServerEndpoint.java

package com.example.websocket;
import javax.websocket.*;
import javax.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("/chatroom")
public class ChatServerEndpoint {
    // 使用一个线程安全的 Set 来存储所有活跃的 WebSocket 会话
    // 注意:对于高并发场景,可能需要考虑更优的并发控制或使用外部存储(如 Redis)
    private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
    /**
     * 当新的 WebSocket 连接建立时调用此方法。
     * @param session 与客户端关联的会话
     * @param config 端点配置
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        chatroomUsers.add(session);
        System.out.println("新用户连接: " + session.getId());
        // 广播新用户加入的消息
        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());
        // 广播用户离开的消息
        broadcastMessage("系统: " + session.getId() + " 已离开聊天室。");
    }
    /**
     * 当 WebSocket 通信过程中发生错误时调用此方法。
     * @param session 出错的会话
     * @param throwable 捕获到的异常
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        System.err.println("WebSocket 错误 - 会话 ID: " + session.getId());
        throwable.printStackTrace();
    }
    /**
     * 向所有连接的客户端广播消息。
     * @param message 要广播的消息
     */
    private void broadcastMessage(String message) {
        // 同步遍历 Set,以避免并发修改问题
        synchronized (chatroomUsers) {
            for (Session user : chatroomUsers) {
                try {
                    // 如果会话是打开的,则发送消息
                    if (user.isOpen()) {
                        user.getBasicRemote().sendText(message);
                    }
                } catch (IOException e) {
                    System.err.println("向用户 " + user.getId() + " 发送消息失败: " + e.getMessage());
                    // 如果发送失败,通常意味着连接已断开,可以将其从 Set 中移除
                    chatroomUsers.remove(user);
                }
            }
        }
    }
}

配置 WebSocket 端点

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

为了让服务器知道这个 @ServerEndpoint,我们需要在 web.xml 中进行配置(虽然 JSR 356 规范要求容器自动发现,但为了兼容性和明确性,最好加上)。

src/main/webapp/WEB-INF/ 目录下创建或修改 web.xml 文件。

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--
        配置 WebSocket 端点。
        <servlet-class> 指向你的端点类。
        <async-supported>true</async-supported> 是必须的,因为 WebSocket 是异步的。
    -->
    <servlet>
        <servlet-name>ChatServerEndpoint</servlet-name>
        <servlet-class>com.example.websocket.ChatServerEndpoint</servlet-class>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>ChatServerEndpoint</servlet-name>
        <url-pattern>/chatroom</url-pattern>
    </servlet-mapping>
</web-app>

第 3 步:前端实现

我们创建一个简单的 HTML 页面作为客户端,这个页面将包含一个聊天窗口和一个输入框。

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

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 {
            width: 70%;
            padding: 8px;
        }
        #send-button {
            width: 28%;
            padding: 8px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        #send-button:hover {
            background-color: #45a049;
        }
        .system-message {
            color: #888;
            font-style: italic;
        }
    </style>
</head>
<body>
    <h1>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>
        // 获取 DOM 元素
        const chatLog = document.getElementById('chat-log');
        const messageInput = document.getElementById('message-input');
        // 创建 WebSocket 连接
        // 注意:这里的 URL 必须与后端 @ServerEndpoint 的 value 值对应
        // ws:// 是 WebSocket 协议,wss:// 是 WebSocket Secure 协议
        const ws = new WebSocket("ws://" + window.location.host + "${pageContext.request.contextPath}/chatroom");
        // 当 WebSocket 连接成功时触发
        ws.onopen = function(event) {
            console.log("WebSocket 连接已建立。");
            appendMessage("系统: 你已成功连接到聊天室。", true);
        };
        // 当收到服务器消息时触发
        ws.onmessage = function(event) {
            console.log("收到消息: " + event.data);
            appendMessage(event.data);
        };
        // 当 WebSocket 连接关闭时触发
        ws.onclose = function(event) {
            console.log("WebSocket 连接已关闭。");
            appendMessage("系统: 与服务器的连接已断开。", true);
        };
        // 当 WebSocket 连接发生错误时触发
        ws.onerror = function(error) {
            console.error("WebSocket 错误:", error);
            appendMessage("系统: 发生连接错误。", true);
        };
        // 发送消息的函数
        function sendMessage() {
            const message = messageInput.value.trim();
            if (message) {
                ws.send(message); // 通过 WebSocket 发送消息
                messageInput.value = ''; // 清空输入框
            }
        }
        // 将消息追加到聊天日志的函数
        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>

注意:在 JSP 中,${pageContext.request.contextPath} 可以正确获取上下文路径,但在纯 HTML 文件中,它不会被解析,为了让这个 HTML 在你的 Web 应用中工作,你可以:

  1. 将其重命名为 index.jsp,并确保你的服务器配置了欢迎文件列表。
  2. 或者,更简单的方式,在 web.xml 中添加 <welcome-file-list>,让 index.html 作为首页。

web.xml (添加欢迎文件列表)

<web-app ...>
    <!-- ... 其他配置 ... -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

第 4 步:部署与测试

  1. 打包项目:在项目根目录下运行 mvn clean package 命令,这会在 target 目录下生成一个 .war 文件(java-websocket-chatroom-1.0-SNAPSHOT.war)。
  2. 部署到 Tomcat
    • 将生成的 .war 文件复制到你的 Tomcat webapps 目录下。
    • 启动 Tomcat 服务器。
  3. 测试
    • 打开你的浏览器,访问 http://localhost:8080/java-websocket-chatroom-1.0-SNAPSHOT/
    • 打开另一个浏览器窗口或标签页,访问相同的地址。
    • 你会看到两个窗口都连接成功,并收到“欢迎”消息。
    • 在任意一个窗口输入消息并发送,消息会立即显示在两个窗口的聊天记录中。
    • 关闭其中一个窗口,另一个窗口会收到“用户已离开”的系统消息。

总结与扩展

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

可以进一步扩展的功能:

  • 用户名:在 onOpen 时让用户输入一个用户名,并广播“用户 [用户名] 加入了聊天室”,而不是使用 session.getId()
  • 私聊:实现发送 @username message 这样的格式,将消息只推送给特定用户。
  • 持久化:将聊天记录保存到数据库中,以便新用户可以查看历史消息。
  • 安全性:使用 @ServerEndpointConfig.Configurator 来进行身份验证和授权,确保只有登录用户才能进入聊天室。
  • 心跳机制:实现 @OnMessage 中的 PongMessage 处理,以检测不活跃的连接并清理它们,防止僵尸连接占用资源。
  • 使用 Spring Boot:如果你使用的是 Spring Boot,集成 WebSocket 会更加简单和强大,它提供了更高级的抽象和更便捷的配置方式。
分享:
扫描分享到社交APP
上一篇
下一篇