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

- 用户进入聊天室,会收到欢迎消息,并通知其他用户。
- 用户发送消息,所有在线用户都能实时看到。
- 用户离开聊天室,会收到离开消息,并通知其他用户。
第一步:项目环境准备
创建 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 文件中包含它们。

配置 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 并显示聊天内容。

在 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)
-
配置 Tomcat/WildFly:
- 在 IntelliJ IDEA 中,打开
Run/Debug Configurations。 - 点击 号,选择
Tomcat Server->Local。 - 为你的服务器命名(如 "WebSocket Chat")。
- 在
Deployment标签页,点击 ,选择Artifact,然后选择你刚刚创建的 Web 项目(通常是your-app-name:war exploded)。 - 应用并保存配置。
- 在 IntelliJ IDEA 中,打开
-
运行:
- 点击右上角的绿色 "Run" 按钮。
- IDE 会启动 Tomcat 服务器,并将你的应用部署上去。
- 浏览器会自动打开
http://localhost:8080/your-app-name/index.html。 - 打开两个或更多浏览器窗口,你就可以看到聊天效果了!
使用 Maven 命令行和嵌入式 Jetty
如果你在 pom.xml 中添加了 Jetty 依赖,你可以创建一个简单的 Java 类来启动服务器。
- 创建一个主类
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();
}
}
- 运行:
- 在 IDE 中直接运行
MainApp的main方法。 - 然后在浏览器中访问
http://localhost:8080。
- 在 IDE 中直接运行
总结与扩展
恭喜!你已经成功创建了一个基于 Java WebSocket 的聊天室。
核心要点回顾:
@ServerEndpoint: 标识这是一个 WebSocket 端点。@OnOpen,@OnMessage,@OnClose,@OnError: 这些注解定义了连接生命周期中的关键事件处理方法。Session对象: 代表与单个客户端的连接,可以用来发送消息(session.getBasicRemote().sendText(...))。- 广播机制: 通过一个静态的
Set<Session>来管理所有连接,实现消息广播。 - 前端
WebSocketAPI: 使用 JavaScript 的WebSocket对象建立连接、收发消息。
可以扩展的功能:
- 用户名: 让用户输入用户名,而不是使用
session.getId()。 - 私聊: 实现用户对用户的私聊功能。
- 在线用户列表: 在页面上显示当前所有在线的用户。
- 消息持久化: 将聊天记录保存到数据库。
- 表情包和文件传输: 扩展消息格式,支持富媒体内容。
- 安全性: 添加身份验证,确保只有登录用户才能进入聊天室。
