一、什么是 WebSocket?为什么需要它?
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 允许服务器主动向客户端推送数据,非常适合实时性要求高的场景。
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 请求-响应(被动) | 全双工(双向主动) |
| 连接状态 | 无状态,短连接 | 有状态,持久连接 |
| 实时性 | 低(需轮询,延迟高) | 高(毫秒级推送) |
| 头部开销 | 大(每次携带完整头信息) | 小(握手后仅需少量字节) |
典型应用场景:即时聊天室、股票行情推送、在线协作编辑、游戏实时状态同步。
二、环境准备与依赖
本教程基于 Spring Boot 3.x (对应 Spring Framework 6.x)和 JDK 17+。
1、创建项目
使用 Spring Initializr 创建项目,勾选以下依赖:
- Spring Web
- Spring WebSocket
- Lombok(可选,用于简化代码)
2、Maven 依赖
确保 pom.xml 中包含以下核心依赖:
<dependencies>
<!-- Web基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
三、核心实现:基于 STOMP 的消息代理模式
Spring 提供了基于 STOMP 协议的 WebSocket 支持,它比原生 API 更易于使用,类似于消息队列的发布/订阅模式。
1、配置 WebSocket
创建一个配置类,启用 WebSocket 消息代理并注册端点。
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 registry) {
// 启用简单的内存消息代理,客户端订阅以 "/topic" 开头的目的地
registry.enableSimpleBroker("/topic");
// 设置客户端发送消息的前缀,即 "/app"
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册端点 "/ws",客户端将通过此路径建立连接
// withSockJS() 提供了对不支持 WebSocket 浏览器的降级支持
registry.addEndpoint("/ws").withSockJS();
}
}
2、编写消息控制器
创建一个控制器来处理客户端的消息发送和广播。
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class ChatController {
/**
* 处理客户端发送的消息
* @MessageMapping("/chat") 对应客户端发送目标 "/app/chat"
* @SendTo("/topic/messages") 将返回值广播给所有订阅 "/topic/messages" 的客户端
*/
@MessageMapping("/chat")
@SendTo("/topic/messages")
public ChatMessage sendChatMessage(ChatMessage message) {
// 这里可以添加业务逻辑,如保存消息到数据库
System.out.println("收到消息: " + message.getContent());
return message;
}
}
3、定义消息实体
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
private String username;
private String content;
private long timestamp;
}
四、前端客户端连接(HTML + JavaScript)
Spring WebSocket 默认支持 SockJS,前端可以使用 sockjs-client 和 stompjs 库进行连接。
1、引入依赖
在 HTML 文件中引入以下 CDN 资源:
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
2、连接与通信代码
<script type="text/javascript">
var stompClient = null;
// 1. 建立连接
function connect() {
// 创建 SockJS 对象,指向后端注册的端点 /ws
var socket = new SockJS('/ws');
// 使用 STOMP 协议包装
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('连接成功: ' + frame);
// 2. 订阅消息主题
stompClient.subscribe('/topic/messages', function (message) {
// 接收服务器广播的消息
showMessage(JSON.parse(message.body));
});
});
}
// 3. 发送消息
function sendMessage() {
var messageContent = document.getElementById('inputMessage').value;
var chatMessage = {
username: 'User_' + Math.floor(Math.random() * 1000),
content: messageContent,
timestamp: new Date().getTime()
};
// 发送到 /app/chat (对应后端的 @MessageMapping("/chat"))
stompClient.send("/app/chat", {}, JSON.stringify(chatMessage));
}
function showMessage(message) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.innerText = message.username + ": " + message.content;
response.appendChild(p);
}
// 页面加载完成后连接
window.onload = connect;
</script>
五、进阶:使用原生 WebSocketHandler
如果你不需要 STOMP 协议,只想处理简单的文本消息,可以使用 WebSocketHandler。
1、自定义 Handler
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
public class MyCustomHandler extends TextWebSocketHandler {
// 线程安全的会话列表
private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
session.sendMessage(new TextMessage("欢迎连接!当前在线人数: " + sessions.size()));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 广播消息给所有连接者
for (WebSocketSession s : sessions) {
if (s.isOpen()) {
s.sendMessage(new TextMessage("广播: " + message.getPayload()));
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
2、注册 Handler
在配置类中注入并注册:
@Configuration
@EnableWebSocket // 注意:这里启用原生 WebSocket 支持
public class NativeWebSocketConfig implements WebSocketConfigurer {
@Autowired
private MyCustomHandler myCustomHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册路径 /ws-native,允许跨域
registry.addHandler(myCustomHandler, "/ws-native").setAllowedOrigins("*");
}
}
六、常见问题排查
-
404 Not Found :检查
@EnableWebSocketMessageBroker或@EnableWebSocket是否遗漏,以及端点路径/ws是否正确。 -
跨域问题 :确保在
registerWebSocketHandlers中配置了.setAllowedOrigins("*")或指定具体域名。 -
握手失败:如果是生产环境使用 Nginx 反向代理,需要配置 Upgrade 头:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
通过以上步骤,你可以在 Spring Boot 3.x 中快速搭建起稳定高效的 WebSocket 服务。