WebSocket实战:构建Spring Boot实时聊天应用
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。本文将基于Spring Boot框架,详细介绍如何实现一个完整的WebSocket聊天应用。
一、WebSocket协议简介
1.1 传统HTTP协议的局限性
- 单向通信:只能由客户端发起请求
- 轮询效率低:需要不断向服务器发送请求
- 实时性差:无法实现服务器主动推送
1.2 WebSocket的优势
- 全双工通信:客户端和服务器可以同时发送和接收数据
- 低延迟:建立连接后,数据传输延迟显著降低
- 减少带宽消耗:避免了不必要的HTTP头部开销
- 持久连接:一次握手,持久连接
二、项目环境搭建
2.1 Maven依赖配置
在pom.xml中配置必要的依赖:
<parent><groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.5.4</version>
</parent><dependencies><!-- Spring Boot核心依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- Web支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- JSON处理 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.7</version></dependency>
</dependencies>2.2 主启动类
@SpringBootApplication
public class WebSocketApplication {public static void main(String[] args) {SpringApplication.run(WebSocketApplication.class, args);}
}三、核心组件实现
3.1 消息实体类
定义消息传输对象:
public class Message {//注解表示该属性可以被序列化也可以被反序列化@Exposeprivate String from; //发送者@Exposeprivate String to; //接收者@Exposeprivate String message; //内容// 省略getter/setter方法
}3.2 WebSocket配置类
配置WebSocket处理器和拦截器:
@Component
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate ChatHandler chatHandler;@Autowiredprivate ChatHandshakeInterceptor chatHandshakeInterceptor;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(chatHandler, "/chat") //与/chat 建立websocket连接.addInterceptors(chatHandshakeInterceptor);}
}3.3 握手拦截器
在连接建立前后进行拦截处理:
@Component
public class ChatHandshakeInterceptor implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {// 可以进行身份验证等操作return true; // 放行连接}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {// 握手成功后的处理}
}四、核心业务逻辑实现
4.1 WebSocket消息处理器
@Component
public class ChatHandler implements WebSocketHandler {// 保存所有用户连接会话public static final Map<String, WebSocketSession> USER_SOCKET_SESSION = new HashMap<>();/*** 连接建立成功回调*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {USER_SOCKET_SESSION.put(session.getId(), session);System.out.println("用户连接成功,会话ID: " + session.getId());}/*** 处理接收到的消息*/@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {String payload = message.getPayload().toString();System.out.println("接收到消息: " + payload);// 反序列化消息Message msg = GsonUtils.fromJson(payload, Message.class);// 根据消息类型进行不同处理if ("All".equals(msg.getTo())) {// 群发消息sendMessageToAll(msg);} else {// 私聊消息sendMessageToUser(msg.getTo(), msg);}}/*** 群发消息给所有用户*/public void sendMessageToAll(Message message) throws IOException {for (WebSocketSession session : USER_SOCKET_SESSION.values()) {if (session.isOpen()) {session.sendMessage(new TextMessage(GsonUtils.toJson(message)));}}}/*** 发送消息给指定用户*/public void sendMessageToUser(String userId, Message message) throws IOException {WebSocketSession session = USER_SOCKET_SESSION.get(userId);if (session != null && session.isOpen()) {session.sendMessage(new TextMessage(GsonUtils.toJson(message)));}}/*** 处理传输错误*/@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("连接出错: " + exception.getMessage());USER_SOCKET_SESSION.remove(session.getId());if (session.isOpen()) {session.close();}}/*** 连接关闭回调*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {USER_SOCKET_SESSION.remove(session.getId());System.out.println("用户断开连接,会话ID: " + session.getId());}@Overridepublic boolean supportsPartialMessages() {return false;}
}4.2 JSON工具类
public class GsonUtils {private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").setPrettyPrinting().create();public static String toJson(Object object) {return GSON.toJson(object);}public static <T> T fromJson(String json, Class<T> classOfT) {return GSON.fromJson(json, classOfT);}
}五、前端实现
5.1 HTML页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WebSocket聊天室</title><style>.chat-container {width: 400px;margin: 0 auto;border: 1px solid #ccc;padding: 20px;}.messages {height: 300px;border: 1px solid #eee;overflow-y: auto;margin-bottom: 10px;padding: 10px;}.input-group {display: flex;}#message {flex: 1;padding: 5px;}button {padding: 5px 15px;margin-left: 10px;}</style>
</head>
<body><div class="chat-container"><div class="messages" id="messages"></div><div class="input-group"><input type="text" id="message" placeholder="输入消息..."><button onclick="sendMessage()">发送</button></div></div><script>// WebSocket连接管理class ChatClient {constructor() {this.websocket = null;this.connect();}connect() {if ("WebSocket" in window) {this.websocket = new WebSocket("ws://localhost:8080/chat");this.websocket.onopen = (event) => {this.addMessage("系统", "连接服务器成功");};this.websocket.onmessage = (event) => {const data = JSON.parse(event.data);this.addMessage(data.from, data.message);};this.websocket.onclose = (event) => {this.addMessage("系统", "连接已断开");};this.websocket.onerror = (event) => {this.addMessage("系统", "连接错误");};} else {alert("浏览器不支持WebSocket");}}sendMessage(message, to = "All") {if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {const data = {message: message,from: "用户" + Math.random().toString(36).substr(2, 5),to: to};this.websocket.send(JSON.stringify(data));}}addMessage(from, message) {const messagesDiv = document.getElementById("messages");const messageDiv = document.createElement("div");messageDiv.innerHTML = `<strong>${from}:</strong> ${message}`;messagesDiv.appendChild(messageDiv);messagesDiv.scrollTop = messagesDiv.scrollHeight;}}// 初始化聊天客户端const chatClient = new ChatClient();function sendMessage() {const messageInput = document.getElementById("message");const message = messageInput.value.trim();if (message) {chatClient.sendMessage(message);messageInput.value = "";}}// 回车发送消息document.getElementById("message").addEventListener("keypress", function(e) {if (e.key === "Enter") {sendMessage();}});</script>
</body>
</html>5.2 页面控制器
@Controller
public class PageController {@RequestMapping("/chat")public String chatPage() {return "chat";}
}六、WebSocket生命周期详解
6.1 连接建立过程
- HTTP握手:客户端发起WebSocket连接请求
- 协议升级:服务器响应101状态码切换协议
- 连接建立:双向通信通道建立
6.2 消息处理流程
- 消息接收:通过
handleMessage方法处理 incoming消息 - 消息解析:将JSON数据反序列化为Java对象
- 业务处理:根据消息类型进行路由分发
- 消息发送:将处理结果发送给目标用户
6.3 连接关闭处理
- 正常关闭:客户端主动断开连接
- 异常关闭:网络异常或服务器错误
- 资源清理:及时移除会话记录,释放资源

七、进阶功能扩展
7.1 用户身份认证
// 在握手拦截器中添加认证逻辑
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {// 提取token进行验证String token = extractToken(request);if (validateToken(token)) {attributes.put("user", getUserFromToken(token));return true;}return false;
}7.2 消息持久化
@Service
public class MessageService {public void saveMessage(Message message) {// 将消息保存到数据库// 可结合Redis实现消息缓存}public List<Message> getHistoryMessages(String userId) {// 获取历史消息return messageRepository.findByToUserOrFromUser(userId);}
}