- 环境准备:创建一个 Maven Web 项目。
- 后端实现:编写 WebSocket 端点和消息处理逻辑。
- 前端实现:创建一个简单的 HTML 页面用于发送和接收消息。
- 部署与测试:将应用打包成 WAR 文件并部署到 Tomcat 服务器上进行测试。
第 1 步:环境准备 (Maven Web 项目)
你需要一个 Java Web 开发环境,并确保你有一个支持 WebSocket 的 Servlet 容器,Apache Tomcat 8+ 或 Jetty 9+。

使用你的 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 连接、消息和断开事件。

创建 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 端点

为了让服务器知道这个 @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 应用中工作,你可以:
- 将其重命名为
index.jsp,并确保你的服务器配置了欢迎文件列表。 - 或者,更简单的方式,在
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 步:部署与测试
- 打包项目:在项目根目录下运行
mvn clean package命令,这会在target目录下生成一个.war文件(java-websocket-chatroom-1.0-SNAPSHOT.war)。 - 部署到 Tomcat:
- 将生成的
.war文件复制到你的 Tomcatwebapps目录下。 - 启动 Tomcat 服务器。
- 将生成的
- 测试:
- 打开你的浏览器,访问
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 会更加简单和强大,它提供了更高级的抽象和更便捷的配置方式。
