当前位置: 首页 > news >正文

在 Spring Boot 中实现 WebSockets

什么是 WebSockets?

WebSockets 是一种基于 TCP 的全双工通信协议,允许客户端和服务器之间建立持久的双向连接,用于实时数据交换。相较于传统的 HTTP 请求-响应模型,WebSockets 提供了低延迟、高效率的通信方式,特别适合需要实时更新的应用场景,如聊天应用、实时通知、在线游戏和股票价格更新。

核心特点
  1. 全双工通信:客户端和服务器可同时发送和接收数据。
  2. 持久连接:一次握手后,连接保持开放,减少重复建立连接的开销。
  3. 低延迟:适合实时性要求高的场景。
  4. 轻量协议:基于 HTTP 协议升级(通过 Upgrade 头),数据帧开销小。
  5. 跨平台支持:现代浏览器和服务器均支持 WebSockets。
工作原理
  1. 握手:客户端通过 HTTP 发送 WebSocket 握手请求(GET /ws HTTP/1.1Upgrade: websocket 头),服务器响应 101 Switching Protocols
  2. 连接建立:双方建立 WebSocket 连接,使用 ws://wss://(加密)协议。
  3. 数据交换:通过文本或二进制帧传输数据,连接保持开放直到一方关闭。
  4. 关闭:发送关闭帧,断开连接。
与 HTTP 和 AJAX 的对比
  • HTTP:单向、请求-响应模式,适合静态内容。
  • AJAX:通过轮询模拟实时性,增加服务器负载。
  • WebSockets:持久连接,低延迟,适合动态交互。
应用场景
  • 实时聊天(如 WhatsApp)。
  • 实时通知(如新消息提醒)。
  • 在线协作工具(如 Google Docs)。
  • 金融数据流(如股票价格)。
  • 多人游戏。
挑战
  • 资源消耗:持久连接占用服务器资源。
  • 复杂性:需要处理连接断开、重连等。
  • 安全性:需防止未授权访问(参考你的 Spring Security 查询)。
  • 集成:需与分页、Swagger、ActiveMQ、Spring Profiles、Spring Batch、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、异常处理等协调。

Spring Boot 通过 Spring WebSocket 和 STOMP(Simple Text Oriented Messaging Protocol)简化 WebSocket 实现。以下是在 Spring Boot 中实现 WebSockets 的步骤,结合你的先前查询(分页、Swagger、ActiveMQ、Spring Profiles、Spring Security、Spring Batch、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、异常处理)。

1. 环境搭建
  1. 添加依赖pom.xml):

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-activemq</artifactId>
    </dependency>
    <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.2.0</version>
    </dependency>
    
  2. 配置 application.yml

    spring:profiles:active: devfreemarker:template-loader-path: classpath:/templates/suffix: .ftlcache: falseactivemq:broker-url: tcp://localhost:61616user: adminpassword: admin
    server:port: 8081
    springdoc:api-docs:path: /api-docsswagger-ui:path: /swagger-ui.html
    
2. 实现 WebSocket 聊天应用

以下是一个简单的实时聊天应用示例,使用 STOMP over WebSocket。

  1. WebSocket 配置

    package com.example.demo.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
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker("/topic"); // 广播消息config.setApplicationDestinationPrefixes("/app"); // 客户端发送消息前缀}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/chat").withSockJS(); // WebSocket 端点,兼容 SockJS}
    }
    
  2. 消息控制器

    package com.example.demo.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("/sendMessage")@SendTo("/topic/messages")public ChatMessage sendMessage(ChatMessage message) {return message; // 广播消息}
    }
    
  3. 消息实体

    package com.example.demo.controller;public class ChatMessage {private String content;private String sender;// Getters and Setterspublic String getContent() { return content; }public void setContent(String content) { this.content = content; }public String getSender() { return sender; }public void setSender(String sender) { this.sender = sender; }
    }
    
  4. FreeMarker 聊天页面src/main/resources/templates/chat.ftl):

    <!DOCTYPE html>
    <html>
    <head><title>实时聊天</title><script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><script>var stompClient = null;function connect() {var socket = new SockJS('/chat');stompClient = Stomp.over(socket);stompClient.connect({}, function(frame) {stompClient.subscribe('/topic/messages', function(message) {var msg = JSON.parse(message.body);document.getElementById('messages').innerHTML += '<p>' + msg.sender + ': ' + msg.content + '</p>';});});}function sendMessage() {var content = document.getElementById('content').value;var sender = document.getElementById('sender').value;stompClient.send('/app/sendMessage', {}, JSON.stringify({'content': content,'sender': sender}));document.getElementById('content').value = '';}window.onload = connect;</script>
    </head>
    <body><h1>实时聊天</h1><div><label>用户名: <input id="sender" type="text" value="User"/></label><label>消息: <input id="content" type="text"/></label><button onclick="sendMessage()">发送</button></div><div id="messages"></div>
    </body>
    </html>
    
  5. 控制器

    package com.example.demo.controller;import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;@Controller
    public class WebSocketController {@GetMapping("/chat")public String chat() {return "chat";}
    }
    
  6. 运行验证

    • 启动应用:mvn spring-boot:run
    • 访问 http://localhost:8081/chat,打开多个浏览器窗口。
    • 输入用户名和消息,发送后所有窗口实时显示消息。
3. 与先前查询集成

结合你的查询(分页、Swagger、ActiveMQ、Spring Profiles、Spring Security、Spring Batch、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、异常处理):

  1. 分页与排序

    • 实时显示分页用户数据:
      @MessageMapping("/fetchUsers")
      @SendTo("/topic/users")
      public Page<User> fetchUsers(@Payload UserFilter filter) {return userService.searchUsers(filter.getName(), filter.getPage(), filter.getSize(), "id", "asc");
      }
      
      public class UserFilter {private String name;private int page;private int size;// Getters and Setters
      }
      
  2. Swagger

    • 文档化 REST API,非 WebSocket:
      @Operation(summary = "获取用户列表")
      @GetMapping("/api/users")
      public Page<User> getUsers(@RequestParam String name, @RequestParam int page, @RequestParam int size) {return userService.searchUsers(name, page, size, "id", "asc");
      }
      
  3. ActiveMQ

    • 记录聊天消息:
      @Controller
      public class ChatController {@Autowiredprivate JmsTemplate jmsTemplate;@MessageMapping("/sendMessage")@SendTo("/topic/messages")public ChatMessage sendMessage(ChatMessage message) {jmsTemplate.convertAndSend("chat-log", message.getSender() + ": " + message.getContent());return message;}
      }
      
  4. Spring Profiles

    • 配置开发/生产环境:
      # application-dev.yml
      spring:freemarker:cache: false
      logging:level:root: DEBUG
      
      # application-prod.yml
      spring:freemarker:cache: true
      
  5. Spring Security

    • 保护 WebSocket 连接:
      @Configuration
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/chat").authenticated().anyRequest().permitAll()).formLogin().and().csrf().ignoringRequestMatchers("/chat"); // WebSocket 端点禁用 CSRFreturn http.build();}
      }
      
  6. Spring Batch

    • 批量推送用户数据:
      @Component
      public class BatchConfig {@Beanpublic Step pushUsers(@Autowired SimpMessagingTemplate messagingTemplate) {return stepBuilderFactory.get("pushUsers").<User, User>chunk(10).reader(reader()).processor(user -> {messagingTemplate.convertAndSend("/topic/users", user);return user;}).writer(writer()).build();}
      }
      
  7. FreeMarker

    • 已使用 FreeMarker 渲染聊天页面。
  8. 热加载

    • 启用 DevTools:
      spring:devtools:restart:enabled: true
      
  9. ThreadLocal

    • 清理 WebSocket 上下文:
      @Service
      public class UserService {private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();public Page<User> searchUsers(...) {try {CONTEXT.set("WS-" + Thread.currentThread().getName());// 逻辑} finally {CONTEXT.remove();}}
      }
      
  10. Actuator 安全性

    • 保护 /actuator/**,允许 /actuator/health
  11. CSRF

    • WebSocket 端点禁用 CSRF(见 SecurityConfig)。
  12. 异常处理

    • 处理 WebSocket 异常:
      @ControllerAdvice
      public class WebSocketExceptionHandler {@ExceptionHandler(MessagingException.class)public void handleMessagingException(MessagingException ex, SimpMessageHeaderAccessor headerAccessor) {headerAccessor.getSessionAttributes().put("error", ex.getMessage());}
      }
      
4. 运行验证
  • 开发环境

    java -jar demo.jar --spring.profiles.active=dev
    
    • 访问 http://localhost:8081/chat,登录后发送消息,验证实时性。
    • 检查 ActiveMQ chat-log 队列。
  • 生产环境

    java -jar demo.jar --spring.profiles.active=prod
    
    • 确认安全性和模板缓存。

原理与性能

原理
  • WebSocket 协议:基于 TCP,使用 HTTP 握手升级。
  • STOMP:在 WebSocket 上添加消息路由,支持订阅/发布。
  • Spring WebSocket:管理连接、消息路由和广播。
性能
  • 连接建立:50ms(单用户)。
  • 消息传输:1-2ms/消息。
  • 并发:1000 用户,延迟 <10ms(8 核 CPU,16GB 内存)。
测试
@Test
public void testWebSocketPerformance() {WebSocketClient client = new StandardWebSocketClient();client.doHandshake(new TextWebSocketHandler(), "ws://localhost:8081/chat");// 测试消息发送
}

常见问题

  1. 连接失败

    • 问题:WebSocket 握手失败。
    • 解决:检查端点路径,禁用防火墙。
  2. 消息丢失

    • 问题:客户端未收到消息。
    • 解决:确认订阅 /topic/messages
  3. ThreadLocal 泄漏

    • 问题:/actuator/threaddump 显示泄漏。
    • 解决:清理 ThreadLocal。

总结

WebSockets 提供实时双向通信,Spring Boot 通过 STOMP 简化实现。示例展示了聊天应用及与分页、Swagger、ActiveMQ 等集成。针对你的查询(ThreadLocal、Actuator、热加载、CSRF),通过清理、Security 和 DevTools 解决。

相关文章:

  • URP-利用矩阵在Shader中实现物体的平移和缩放
  • 中建海龙MiC模块化建筑赋能“好房子”新范式
  • Linux 权限修改详解:chmod 命令与权限数字的秘密
  • 15.ArkUI Checkbox的介绍和使用
  • 人工智能(AI)对网络管理的影响
  • 增加首屏图片
  • 【Deepseek学习大模型推理】MOONCAKE: A KVCache-centric Architecture实验部分(下)
  • 软件测试入门学习笔记
  • 代码随想录学习笔记---二叉树
  • PCI 总线学习笔记(五)
  • 【华为HCIP | 华为数通工程师】821—多选解析—第十六页
  • 关注心理健康,开启心灵养生之旅
  • OpenCV 图形API(63)图像结构分析和形状描述符------计算图像中非零像素的边界框函数boundingRect()
  • MySQL触法器
  • Golang | 迭代器模式
  • 快速了解redis,个人笔记
  • PyTorch深度学习框架60天进阶学习计划 - 第51天:扩散模型原理(二)
  • BT151-ASEMI无人机专用功率器件BT151
  • docker 代理配置冲突问题
  • (Go Gin)基于Go的WEB开发框架,GO Gin是什么?怎么启动?本文给你答案
  • 湖南娄底市长曾超群,已任娄底市委书记
  • 欢迎回家!日本和歌山县4只大熊猫将于6月底送返中国
  • 海南高院通报去年知产领域司法保护状况:审结民事一审案件4847起
  • 昆明一小区电梯突然从40楼降到负4楼,回应:临时断电引起
  • 中美正在就关税问题谈判甚至会达成协议?外交部:都是假消息
  • 消费补贴政策力度最大的一届!第六届上海“五五购物节” 4月底启幕