WebSocket 是后端推送消息到前端的标准方案——聊天室、消息通知、实时数据看板都离不开它。相比轮询WebSocket 在建立一次连接后服务端可以随时主动推送数据实时性更高、资源消耗更低。一、WebSocket 与轮询的对比轮询Polling 客户端 → 请求 → 服务端有数据吗 ← 响应没有 客户端 → 请求 → 服务端有数据吗 ← 响应有了 缺点频繁请求浪费资源实时性差 WebSocket 客户端 → 建立连接 → 服务端 ← 推送数据有新数据时主动发 ← 推送数据 优点连接保持服务端主动推送实时性好二、SpringBoot 整合 WebSocket1. 引入依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependency2. WebSocket 配置ConfigurationEnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 客户端订阅消息的前缀registry.enableSimpleBroker(/topic,/queue);// 客户端发送消息的前缀registry.setApplicationDestinationPrefixes(/app);// 点对点推送前缀registry.setUserDestinationPrefix(/user);}OverridepublicvoidregisterStompEndpoints(StompEndpointRegistryregistry){// WebSocket 连接端点registry.addEndpoint(/ws).setAllowedOriginPatterns(*).withSockJS();// 兼容不支持 WebSocket 的浏览器}}3. 前端连接!-- 引入 SockJS 和 STOMP --scriptsrchttps://cdn.jsdelivr.net/npm/sockjs-client1/dist/sockjs.min.js/scriptscriptsrchttps://cdn.jsdelivr.net/npm/stompjs2.3.3/lib/stomp.min.js/scriptscript// 建立连接varsocketnewSockJS(/ws);varstompClientStomp.over(socket);stompClient.connect({},function(frame){console.log(连接成功: frame);// 订阅广播消息stompClient.subscribe(/topic/notice,function(message){vardataJSON.parse(message.body);alert(收到通知: data.content);});// 订阅个人消息stompClient.subscribe(/user/queue/message,function(message){vardataJSON.parse(message.body);alert(个人消息: data.content);});});// 发送消息functionsendMessage(){stompClient.send(/app/sendMessage,{},JSON.stringify({toUserId:1002,content:你好}));}/script三、服务端推送1. 广播消息推送给所有用户ServicepublicclassWebSocketService{AutowiredprivateSimpMessagingTemplatemessagingTemplate;/** * 广播通知所有在线用户都能收到 */publicvoidbroadcast(Stringtype,Stringcontent){MapString,ObjectmessagenewHashMap();message.put(type,type);message.put(content,content);message.put(timestamp,LocalDateTime.now().toString());// 推送到 /topic/notice所有订阅了该地址的客户端都会收到messagingTemplate.convertAndSend(/topic/notice,message);}}2. 点对点消息推送给指定用户/** * 发送给指定用户 */publicvoidsendToUser(LonguserId,Stringcontent){MapString,ObjectmessagenewHashMap();message.put(from,系统);message.put(content,content);message.put(timestamp,LocalDateTime.now().toString());// 推送到 /user/{userId}/queue/messagemessagingTemplate.convertAndSendToUser(userId.toString(),/queue/message,message);}3. Controller 触发推送RestControllerRequestMapping(/api/ws)publicclassWebSocketController{AutowiredprivateWebSocketServicewsService;/** * 发送广播通知 */PostMapping(/broadcast)publicResultVO?broadcast(RequestBodyNoticeDTOdto){wsService.broadcast(notice,dto.getContent());returnResultVO.success(广播已发送);}/** * 发送给指定用户 */PostMapping(/send)publicResultVO?sendToUser(RequestBodyMessageDTOdto){wsService.sendToUser(dto.getUserId(),dto.getContent());returnResultVO.success(消息已发送);}}四、秒杀系统中的 WebSocket 应用秒杀下单后不需要前端轮询查结果服务端处理完成后主动推送ServicepublicclassSeckillService{AutowiredprivateSimpMessagingTemplatemessagingTemplate;publicResultVO?doSeckill(LongproductId,LonguserId,StringuserName){// ... 秒杀核心逻辑 ...// 异步处理完成后推送结果给用户CompletableFuture.runAsync(()-{// 模拟异步处理try{Thread.sleep(2000);}catch(Exceptione){}MapString,ObjectresultnewHashMap();result.put(productId,productId);result.put(status,success);result.put(orderNo,order.getOrderNo());messagingTemplate.convertAndSendToUser(userId.toString(),/queue/seckill,result);});returnResultVO.success(订单处理中请稍候...);}}前端订阅秒杀结果stompClient.subscribe(/user/queue/seckill,function(message){vardataJSON.parse(message.body);if(data.statussuccess){showResult(,秒杀成功,订单号: data.orderNo);}else{showResult(,秒杀失败,);}});五、WebSocket 事件监听ComponentpublicclassWebSocketEventListener{privatestaticfinalLoggerlogLoggerFactory.getLogger(WebSocketEventListener.class);/** * 监听连接建立 */EventListenerpublicvoidhandleConnect(SessionConnectedEventevent){log.info(WebSocket 连接建立);}/** * 监听断开连接 */EventListenerpublicvoidhandleDisconnect(SessionDisconnectEventevent){StompHeaderAccessorheaderAccessorStompHeaderAccessor.wrap(event.getMessage());StringsessionIdheaderAccessor.getSessionId();log.info(WebSocket 断开连接: {},sessionId);}}六、在线用户管理ComponentpublicclassUserSessionManager{/** 在线用户映射userId → sessionId */privatefinalMapLong,StringonlineUsersnewConcurrentHashMap();/** * 用户上线 */publicvoiduserOnline(LonguserId,StringsessionId){onlineUsers.put(userId,sessionId);}/** * 用户下线 */publicvoiduserOffline(StringsessionId){onlineUsers.entrySet().removeIf(e-e.getValue().equals(sessionId));}/** * 获取在线人数 */publicintgetOnlineCount(){returnonlineUsers.size();}/** * 判断用户是否在线 */publicbooleanisOnline(LonguserId){returnonlineUsers.containsKey(userId);}}七、常见问题1. Nginx 代理配置server { listen 80; server_name example.com; location /ws { proxy_pass http://localhost:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }2. 心跳检测防止连接因长时间空闲而被断开// WebSocket 配置中设置心跳registry.enableSimpleBroker(/topic,/queue).setHeartbeatValue(newlong[]{10000,10000});// 10秒心跳3. 集群环境多实例部署时需要消息中间件广播消息ConfigurationEnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 使用 RabbitMQ 作为消息代理集群环境registry.enableStompBrokerRelay(/topic,/queue).setRelayHost(localhost).setRelayPort(61613).setSystemLogin(guest).setSystemPasscode(guest);}}八、与轮询的区别总结对比WebSocketHTTP 轮询连接方式长连接一次握手短连接每次请求新建数据流向双向服务端可主动推客户端请求→服务端响应实时性毫秒级取决于轮询间隔资源消耗低连接保持高频繁建连拆连实现复杂度中等简单 觉得有用的话点赞 关注【张老师技术栈】吧每周更新 Java/Python/爬虫 实战干货不让你白来。
SpringBoot 整合 WebSocket——实时消息推送实战
WebSocket 是后端推送消息到前端的标准方案——聊天室、消息通知、实时数据看板都离不开它。相比轮询WebSocket 在建立一次连接后服务端可以随时主动推送数据实时性更高、资源消耗更低。一、WebSocket 与轮询的对比轮询Polling 客户端 → 请求 → 服务端有数据吗 ← 响应没有 客户端 → 请求 → 服务端有数据吗 ← 响应有了 缺点频繁请求浪费资源实时性差 WebSocket 客户端 → 建立连接 → 服务端 ← 推送数据有新数据时主动发 ← 推送数据 优点连接保持服务端主动推送实时性好二、SpringBoot 整合 WebSocket1. 引入依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependency2. WebSocket 配置ConfigurationEnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 客户端订阅消息的前缀registry.enableSimpleBroker(/topic,/queue);// 客户端发送消息的前缀registry.setApplicationDestinationPrefixes(/app);// 点对点推送前缀registry.setUserDestinationPrefix(/user);}OverridepublicvoidregisterStompEndpoints(StompEndpointRegistryregistry){// WebSocket 连接端点registry.addEndpoint(/ws).setAllowedOriginPatterns(*).withSockJS();// 兼容不支持 WebSocket 的浏览器}}3. 前端连接!-- 引入 SockJS 和 STOMP --scriptsrchttps://cdn.jsdelivr.net/npm/sockjs-client1/dist/sockjs.min.js/scriptscriptsrchttps://cdn.jsdelivr.net/npm/stompjs2.3.3/lib/stomp.min.js/scriptscript// 建立连接varsocketnewSockJS(/ws);varstompClientStomp.over(socket);stompClient.connect({},function(frame){console.log(连接成功: frame);// 订阅广播消息stompClient.subscribe(/topic/notice,function(message){vardataJSON.parse(message.body);alert(收到通知: data.content);});// 订阅个人消息stompClient.subscribe(/user/queue/message,function(message){vardataJSON.parse(message.body);alert(个人消息: data.content);});});// 发送消息functionsendMessage(){stompClient.send(/app/sendMessage,{},JSON.stringify({toUserId:1002,content:你好}));}/script三、服务端推送1. 广播消息推送给所有用户ServicepublicclassWebSocketService{AutowiredprivateSimpMessagingTemplatemessagingTemplate;/** * 广播通知所有在线用户都能收到 */publicvoidbroadcast(Stringtype,Stringcontent){MapString,ObjectmessagenewHashMap();message.put(type,type);message.put(content,content);message.put(timestamp,LocalDateTime.now().toString());// 推送到 /topic/notice所有订阅了该地址的客户端都会收到messagingTemplate.convertAndSend(/topic/notice,message);}}2. 点对点消息推送给指定用户/** * 发送给指定用户 */publicvoidsendToUser(LonguserId,Stringcontent){MapString,ObjectmessagenewHashMap();message.put(from,系统);message.put(content,content);message.put(timestamp,LocalDateTime.now().toString());// 推送到 /user/{userId}/queue/messagemessagingTemplate.convertAndSendToUser(userId.toString(),/queue/message,message);}3. Controller 触发推送RestControllerRequestMapping(/api/ws)publicclassWebSocketController{AutowiredprivateWebSocketServicewsService;/** * 发送广播通知 */PostMapping(/broadcast)publicResultVO?broadcast(RequestBodyNoticeDTOdto){wsService.broadcast(notice,dto.getContent());returnResultVO.success(广播已发送);}/** * 发送给指定用户 */PostMapping(/send)publicResultVO?sendToUser(RequestBodyMessageDTOdto){wsService.sendToUser(dto.getUserId(),dto.getContent());returnResultVO.success(消息已发送);}}四、秒杀系统中的 WebSocket 应用秒杀下单后不需要前端轮询查结果服务端处理完成后主动推送ServicepublicclassSeckillService{AutowiredprivateSimpMessagingTemplatemessagingTemplate;publicResultVO?doSeckill(LongproductId,LonguserId,StringuserName){// ... 秒杀核心逻辑 ...// 异步处理完成后推送结果给用户CompletableFuture.runAsync(()-{// 模拟异步处理try{Thread.sleep(2000);}catch(Exceptione){}MapString,ObjectresultnewHashMap();result.put(productId,productId);result.put(status,success);result.put(orderNo,order.getOrderNo());messagingTemplate.convertAndSendToUser(userId.toString(),/queue/seckill,result);});returnResultVO.success(订单处理中请稍候...);}}前端订阅秒杀结果stompClient.subscribe(/user/queue/seckill,function(message){vardataJSON.parse(message.body);if(data.statussuccess){showResult(,秒杀成功,订单号: data.orderNo);}else{showResult(,秒杀失败,);}});五、WebSocket 事件监听ComponentpublicclassWebSocketEventListener{privatestaticfinalLoggerlogLoggerFactory.getLogger(WebSocketEventListener.class);/** * 监听连接建立 */EventListenerpublicvoidhandleConnect(SessionConnectedEventevent){log.info(WebSocket 连接建立);}/** * 监听断开连接 */EventListenerpublicvoidhandleDisconnect(SessionDisconnectEventevent){StompHeaderAccessorheaderAccessorStompHeaderAccessor.wrap(event.getMessage());StringsessionIdheaderAccessor.getSessionId();log.info(WebSocket 断开连接: {},sessionId);}}六、在线用户管理ComponentpublicclassUserSessionManager{/** 在线用户映射userId → sessionId */privatefinalMapLong,StringonlineUsersnewConcurrentHashMap();/** * 用户上线 */publicvoiduserOnline(LonguserId,StringsessionId){onlineUsers.put(userId,sessionId);}/** * 用户下线 */publicvoiduserOffline(StringsessionId){onlineUsers.entrySet().removeIf(e-e.getValue().equals(sessionId));}/** * 获取在线人数 */publicintgetOnlineCount(){returnonlineUsers.size();}/** * 判断用户是否在线 */publicbooleanisOnline(LonguserId){returnonlineUsers.containsKey(userId);}}七、常见问题1. Nginx 代理配置server { listen 80; server_name example.com; location /ws { proxy_pass http://localhost:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }2. 心跳检测防止连接因长时间空闲而被断开// WebSocket 配置中设置心跳registry.enableSimpleBroker(/topic,/queue).setHeartbeatValue(newlong[]{10000,10000});// 10秒心跳3. 集群环境多实例部署时需要消息中间件广播消息ConfigurationEnableWebSocketMessageBrokerpublicclassWebSocketConfigimplementsWebSocketMessageBrokerConfigurer{OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 使用 RabbitMQ 作为消息代理集群环境registry.enableStompBrokerRelay(/topic,/queue).setRelayHost(localhost).setRelayPort(61613).setSystemLogin(guest).setSystemPasscode(guest);}}八、与轮询的区别总结对比WebSocketHTTP 轮询连接方式长连接一次握手短连接每次请求新建数据流向双向服务端可主动推客户端请求→服务端响应实时性毫秒级取决于轮询间隔资源消耗低连接保持高频繁建连拆连实现复杂度中等简单 觉得有用的话点赞 关注【张老师技术栈】吧每周更新 Java/Python/爬虫 实战干货不让你白来。