- WebSocket 核心概念:为什么需要它,它解决了什么问题。
- Java WebSocket 推送的两种主流实现方式:
- Java-WebSocket (纯 Java 实现)
- Spring Boot + STOMP (更现代、功能更强大的方式)
- 一个完整的实战案例:使用 Spring Boot + STOMP 实现一个简单的聊天室应用。
- 生产环境中的关键考量:心跳、安全、集群部署等。
WebSocket 核心概念
为什么需要 WebSocket?
传统的 HTTP 协议是“请求-响应”模式的,服务器不能主动向客户端推送数据,如果客户端需要实时获取服务器更新,通常只能采用以下两种方式:

- 轮询:客户端定时(比如每秒)向服务器发送 HTTP 请求,询问是否有新数据,这种方式效率低下,会产生大量无用的请求,增加服务器负担。
- 长轮询:客户端发送一个请求,服务器保持连接打开,直到有新数据可用或超时,这比轮询好,但连接仍然是半双工的,且服务器管理大量长连接也有性能开销。
WebSocket 如何解决?
WebSocket 在客户端和服务器之间建立一个持久的长连接,并且是全双工的(Full-Duplex),意味着双方可以同时发送和接收数据,一旦连接建立,服务器就可以随时向客户端推送消息,无需客户端先请求。
工作流程简述:
- 握手:客户端通过一个 HTTP 请求发起 WebSocket 握手,请求头中包含
Upgrade: websocket和Connection: Upgrade。 - 升级:服务器如果支持 WebSocket,会返回一个
101 Switching Protocols响应,表示协议从 HTTP 升级到 WebSocket。 - 通信:之后,双方就可以在这个连接上自由地发送和接收消息,数据帧通常是轻量级的。
Java WebSocket 推送的两种主流实现方式
Java-WebSocket (纯 Java 实现)
这是一个轻量级的、不依赖任何框架的 Java WebSocket 库,适合学习 WebSocket 底层原理或构建简单的应用。
特点:

- 纯 Java 实现,无外部依赖(除了库本身)。
- API 较为底层,需要手动处理连接、消息的接收和发送。
- 代码量相对较多。
基本用法示例:
-
添加依赖 (Maven):
<dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.5.3</version> </dependency> -
创建 WebSocket 服务器:
import org.java_websocket.server.WebSocketServer; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.WebSocket; import java.net.InetSocketAddress; public class MyWebSocketServer extends WebSocketServer { public MyWebSocketServer(int port) { super(new InetSocketAddress(port)); } // 当有新的客户端连接时调用 @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { System.out.println("新连接已建立: " + conn.getRemoteSocketAddress()); // 向新连接的客户端发送欢迎消息 conn.send("欢迎连接到 WebSocket 服务器!"); } // 当客户端断开连接时调用 @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { System.out.println("连接已关闭: " + conn.getRemoteSocketAddress()); } // 当收到客户端消息时调用 @Override public void onMessage(WebSocket conn, String message) { System.out.println("收到来自 " + conn.getRemoteSocketAddress() + " 的消息: " + message); // 将收到的消息广播给所有连接的客户端 this.broadcast("服务器收到消息: " + message); } // 当发生错误时调用 @Override public void onError(WebSocket conn, Exception ex) { ex.printStackTrace(); } // 启动服务器 public static void main(String[] args) { int port = 8887; MyWebSocketServer server = new MyWebSocketServer(port); server.start(); System.out.println("WebSocket 服务器已启动,监听端口: " + port); } }
Spring Boot + STOMP (推荐)
这是目前最主流、最推荐的方式,Spring 对 WebSocket 提供了非常完善的支持,并在此基础上引入了 STOMP 协议。
为什么推荐 Spring Boot + STOMP?
- STOMP 协议:WebSocket 本身只定义了数据帧格式,而 STOMP (Simple Text Oriented Messaging Protocol) 是一个简单的消息协议,它为 WebSocket 提供了消息语义,类似于 JMS 或 AMQP,这使得消息的路由、订阅和广播变得更加结构化和可控。
- 与 Spring 生态集成:完美集成 Spring MVC、Spring Security 等,你可以用熟悉的
@Controller、@MessageMapping等注解来处理 WebSocket 消息。 - 功能强大:支持点对点消息、广播消息、消息代理(如 RabbitMQ, ActiveMQ)等。
- 简化开发:大大减少了底层代码,让开发者可以更专注于业务逻辑。
核心组件:
@ServerEndpoint(不推荐):Tomcat 自带的 WebSocket API,基于方法级别,与 Spring 整合不深。WebSocketMessageBrokerConfigurer(推荐):Spring 的配置方式,基于@Configuration,更符合 Spring 的编程模型。@Controller+@MessageMapping:用于处理客户端发送过来的 STOMP 消息。SimpMessagingTemplate:一个模板类,方便在服务端(非 WebSocket 处理方法中)主动向客户端推送消息。@SendTo:注解在方法上,表示方法的返回值将自动发送到指定的目的地。
完整实战案例:Spring Boot + STOMP 聊天室
我们将创建一个简单的聊天应用,用户可以加入一个公共聊天室,并实时看到所有人的消息。
步骤 1:创建 Spring Boot 项目
在 Spring Initializr 中添加以下依赖:
Spring WebSpring WebSocketSpring Stomp(可选,但通常和 WebSocket 一起使用)Lombok(可选,简化代码)
步骤 2:启用 WebSocket 和 STOMP
创建一个配置类来注册 STOMP 端点并启用消息代理。
// package com.example.websocketdemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker // 启用 WebSocket 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启动一个简单的内存消息代理,用于处理以 "/topic" 为前缀的消息
config.enableSimpleBroker("/topic");
// 设置应用程序的目的地前缀,以 "/app" 开头的消息将被 @Controller 处理
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册一个名为 "/chat" 的端点,并启用 SockJS
// SockJS 是为了在不支持 WebSocket 的浏览器上提供类似 WebSocket 的体验
registry.addEndpoint("/chat").withSockJS();
}
}
步骤 3:创建消息处理控制器
这个控制器将处理客户端发来的消息,并将广播后的消息发送回 /topic/messages 主题。
// package com.example.websocketdemo.controller;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class ChatController {
// @MessageMapping 映射到 "/app/chat.sendMessage" (因为前缀是 /app)
// 这个方法会在收到客户端发送到这个地址的消息时被调用
@MessageMapping("/sendMessage")
// @SendTo 将方法的返回值自动发送到这个主题
// 所有订阅了 "/topic/messages" 的客户端都会收到这条消息
@SendTo("/topic/messages")
public Message sendMessage(Message message) {
// 这里可以添加业务逻辑,比如保存消息到数据库
System.out.println("收到消息: " + message.getContent());
return message; // 返回的消息将被广播
}
}
// 消息实体类
class Message {
private String from;
private String content;
private String type; // e.g., "JOIN", "LEAVE", "CHAT"
// Getters and Setters (Lombok can be used here)
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
步骤 4:创建前端页面 (HTML + JavaScript)
在 src/main/resources/static 目录下创建 index.html。
<!DOCTYPE html>
<html>
<head>Spring Boot WebSocket 聊天室</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#chat { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
#message { width: 80%; padding: 5px; }
#sendButton { width: 18%; padding: 5px; }
.message { margin-bottom: 5px; }
</style>
</head>
<body>
<h2>WebSocket 聊天室</h2>
<div id="chat"></div>
<div>
<input type="text" id="message" placeholder="输入消息...">
<button id="sendButton">发送</button>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const chat = document.getElementById('chat');
const messageInput = document.getElementById('message');
const sendButton = document.getElementById('sendButton');
// 1. 连接 SockJS
const socket = new SockJS('/chat');
// 2. 连接 STOMP 客户端
const stompClient = Stomp.over(socket);
// 3. 连接 WebSocket 服务器
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
// 4. 订阅 /topic/messages 主题
stompClient.subscribe('/topic/messages', function (message) {
// 收到消息后,将其显示在页面上
showMessage(JSON.parse(message.body));
});
});
// 发送消息按钮点击事件
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendMessage();
}
});
function sendMessage() {
const content = messageInput.value.trim();
if (content) {
// 构造消息对象
const message = {
from: 'User-' + Math.floor(Math.random() * 1000), // 模拟用户名
content: content,
type: 'CHAT'
};
// 5. 发送消息到 /app/sendDestination
stompClient.send("/app/sendMessage", {}, JSON.stringify(message));
messageInput.value = '';
}
}
function showMessage(message) {
const messageElement = document.createElement('div');
messageElement.className = 'message';
messageElement.textContent = `${message.from}: ${message.content}`;
chat.appendChild(messageElement);
chat.scrollTop = chat.scrollHeight;
}
});
</script>
</body>
</html>
步骤 5:运行和测试
- 运行 Spring Boot 应用。
- 打开两个浏览器窗口,分别访问
http://localhost:8080。 - 在一个窗口输入消息并发送,另一个窗口会实时看到这条消息。
生产环境中的关键考量
-
心跳检测:WebSocket 长连接可能会因为网络问题(如NAT超时)而断开,但客户端和服务器都不知道,STOMP 协议内置了心跳机制,需要在客户端和服务器配置中启用,以保持连接活性。
- 客户端:
stompClient.connect(headers, connectCallback, errorCallback, {'heart.beat': [10000, 10000]}); - 服务器: 在
WebSocketMessageBrokerConfigurer中配置simpleBroker.setHeartbeatValue(new long[]{10000, 10000});
- 客户端:
-
安全性:
- HTTPS:在生产环境中,WebSocket 连接必须使用
wss://(WebSocket Secure),这要求你的网站也必须使用https://。 - 认证授权:Spring Security 可以轻松集成到 WebSocket 中,你可以在握手阶段或连接建立后对用户进行身份验证,并使用
@PreAuthorize等注解来控制用户是否有权订阅某个主题或发送消息。
- HTTPS:在生产环境中,WebSocket 连接必须使用
-
集群部署:当你的应用部署在多台服务器上时,一个用户可能连接到服务器 A,而消息是由服务器 B 产生的,服务器 B 无法直接推送给连接在 A 上的用户,这时需要一个外部消息代理,如 RabbitMQ 或 ActiveMQ。
- Spring 的
enableSimpleBroker默认使用内存代理,不适用于集群。 - 需要配置
MessageBrokerRegistry使用代理,config.enableStompBrokerRelay("/topic", "/queue") .setRelayHost(rabbitmqHost) .setRelayPort(amqpPort) .setClientLogin(username) .setClientPass(password);
- Spring 的
-
性能和监控:
- 监控活跃的 WebSocket 连接数、消息吞吐量等指标。
- 考虑连接的并发限制,避免单个服务器资源耗尽。
- 对于大量连接,注意 JVM 内存和 GC 压力。
| 特性 | Java-WebSocket | Spring Boot + STOMP |
|---|---|---|
| 易用性 | 较低,API 底层 | 高,注解驱动,与 Spring 生态无缝集成 |
| 功能 | 基础的收发消息 | 强大,支持 STOMP 协议、消息代理、点对点/广播 |
| 依赖 | 仅 Java-WebSocket 库 | 需要 Spring Boot 全家桶 |
| 适用场景 | 学习、简单原型、轻量级应用 | 生产环境、复杂业务应用、企业级项目 |
对于绝大多数 Java 后端项目,强烈推荐使用 Spring Boot + STOMP 的方式来构建 WebSocket 应用,它能为你提供一个健壮、可扩展且易于维护的实时通信解决方案。
