在 Spring Boot 中实现 WebSockets

news2025/4/27 2:47:34

什么是 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: dev
      freemarker:
        template-loader-path: classpath:/templates/
        suffix: .ftl
        cache: false
      activemq:
        broker-url: tcp://localhost:61616
        user: admin
        password: admin
    server:
      port: 8081
    springdoc:
      api-docs:
        path: /api-docs
      swagger-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 {
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            config.enableSimpleBroker("/topic"); // 广播消息
            config.setApplicationDestinationPrefixes("/app"); // 客户端发送消息前缀
        }
    
        @Override
        public 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 Setters
        public 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 {
          @Autowired
          private 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 {
          @Bean
          public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
              http
                  .authorizeHttpRequests(auth -> auth
                      .requestMatchers("/chat").authenticated()
                      .anyRequest().permitAll()
                  )
                  .formLogin()
                  .and()
                  .csrf().ignoringRequestMatchers("/chat"); // WebSocket 端点禁用 CSRF
              return http.build();
          }
      }
      
  6. Spring Batch

    • 批量推送用户数据:
      @Component
      public class BatchConfig {
          @Bean
          public 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 解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2343702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stone 3d v3.3.0版本发布,含时间线和连接器等新功能

1.新加了时间线&#xff08;timeline&#xff09;编辑器&#xff0c;可以类似blender一样给对象制作动画 2.新加了度量&#xff08;metrics&#xff09;系统&#xff0c;通过scene对象检测器中的useMetrics属性来启用或禁用&#xff0c;启用时所选物体将显示三维度量数据 新加了…

.whl文件

本文主要介绍了.whl文件的定义&#xff0c;怎么安装.whl文件&#xff08;离线&#xff0c;在线&#xff09;。 怎么查看cuda的版本&#xff0c;以及如何安装相应版本的cuda&#xff08;本地电脑&#xff0c;超算上&#xff09; 以及如何创建.whl文件 .whl文件的定义 Document…

Git命令行中vim的操作

Git命令行用vim打开文件&#xff0c;或者用其他git命令打开了文件&#xff0c;需要编辑和保存文件等&#xff0c;有些命令表情奇怪&#xff0c;往往容易忘记这些命令。记录下。 下面这篇比较实用和简练&#xff1a; gitvim编辑文件命令 • Worktile社区https://worktile.com/…

C#初级知识总结

一、什么是CIL 1.CIL(Common Intermidate Language)是指.Net的公共中间语言&#xff0c;它是一种编程语言。 .Net框架的各种语言在编译时都会编译成同一种中间语言&#xff08;CIL&#xff09;&#xff0c;之后程序运行的时候CIL会被JIT(Just In Time)转换为二进制语言&#xf…

Linux学习笔记之环境变量

写这篇博客的目的主要是因为本人学习动静态库时&#xff0c;用到了环境变量的知识&#xff0c;发现略有遗忘&#xff0c;因此回顾复习&#xff0c;整理成博客。 一、环境变量是什么 Linux环境变量是存储系统或程序运行时配置信息的特殊变量&#xff0c;用于为程序提供配置参数…

16:00开始面试,16:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到4月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

私钥连接服务器(已经有服务器私钥

前言&#xff1a;假设我们已经有了服务器的私钥&#xff0c;我们怎么配置呢&#xff1f; 下面我会从vsc的配置角度来写 ✅ 步骤一&#xff1a;准备工作 安装 VS Code&#xff08;如果还没装&#xff09; &#x1f449; https://code.visualstudio.com/ 安装插件&#xff1a;Re…

学员答题pk知识竞赛小程序怎么做

制作学员答题PK知识竞赛小程序&#xff0c;主要有以下步骤&#xff1a; 一、规划设计 明确需求&#xff1a;确定小程序的使用场景是校园知识竞赛、培训机构考核还是企业内部培训等。答题功能&#xff0c;规定答题的具体规则&#xff0c;包括题目类型&#xff08;单选、多选、…

外观模式:简化复杂系统接口的设计模式

外观模式&#xff1a;简化复杂系统接口的设计模式 一、模式核心&#xff1a;为复杂子系统提供统一简单接口 当一个系统由多个复杂子系统组成时&#xff08;如电商系统中的支付、物流、库存模块&#xff09;&#xff0c;客户端直接调用子系统会导致依赖关系复杂、代码难以维护…

uniapp-商城-36-shop 购物车 选好了 进行订单确认2 支付方式颜色变化和颜色滤镜filter

颜色滤镜&#xff0c;在好多网页都这样使用&#xff0c;滤掉彩色&#xff0c;显示黑白&#xff0c;这在一些关键的日子中都这样使用。 1、依然回到订单确认页面 看到支付的颜色了嘛&#xff1f; <view class"payType"><view class"box" :class&q…

Vue3 上传后的文件智能预览(实战体会)

目录 前言1. Demo12. Demo2 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器&#xff0c;无代码爬取&#xff0c;就来&#xff1a;bright.cn 此处的基本知识涉及较少&#xff0c;主要以Demo的形式供大…

CCE13.【C++ Cont】练习题组13 静态链表专题

目录 1.B3630 排队顺序 题目 分析 代码 提交结果 2.B3631 单向链表 题目 分析 前置知识:map数组加快访问速度(简单的哈希表优化) 使用map数组的重要提醒 代码 提交结果 3.★P1160 队列安排 题目 分析 方法1:带头不循环双向链表的设计 方法2:带头循环的双向链表…

内联函数(c++)

预处理&#xff1a;优点&#xff1a;内嵌到目标代码&#xff0c;减少函数的调用。 缺点&#xff1a;在预处理阶段完成替换&#xff0c;避免了语义上的差错。 egg&#xff1a; #define SQR(X) ((X)*(X)) 函数&#xff1a;优点&#xff1a;完成了某一类操作的抽象&#xff0c;…

R7周:糖尿病预测模型优化探索

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 一、数据预处理 1.设置GPU import torch.nn.functional as F import torch.nn as nn import torch, torchvisiondevice torch.device("cuda"…

线程怎么创建?Java 四种方式一网打尽

&#x1f680; Java 中线程的 4 种创建方式详解 创建方式实现方式是否推荐场景说明1. 继承 Thread 类class MyThread extends Thread❌ 不推荐简单学习、单线程场景2. 实现 Runnable 接口class MyRunnable implements Runnable✅ 推荐更适合多线程共享资源3. 实现 Callable 接…

STM32之DHT11温湿度传感器---附代码

DHT11简介 DHT11的供电电压为 3&#xff0d;5.5V。 传感器上电后&#xff0c;要等待 1s 以越过不稳定状态在此期间无需发送任何指令。 电源引脚&#xff08;VDD&#xff0c;GND&#xff09;之间可增加一个100nF 的电容&#xff0c;用以去耦滤波。 DATA 用于微处理器与DHT11之间…

工业相机——镜头篇【机器视觉,图像采集系统,成像原理,光学系统,成像光路,镜头光圈,镜头景深,远心镜头,分辨率,MTF曲线,焦距计算 ,子午弧矢】

文章目录 1 机器视觉&#xff0c;图像采集系统2 相机镜头&#xff0c;属于一种光学系统3 常规镜头 成像光路4 镜头光圈5 镜头的景深6 远心镜头 及 成像原理7 远心镜头种类 及 应用场景8 镜头分辨率10 镜头的对比度11 镜头的MTF曲线12 镜头的焦距 计算13 子午弧矢 图解 反差 工业…

openwrt查询网关的命令

方法一&#xff1a;route -n 方法二&#xff1a;ip route show

华为OD机试真题——查找接口成功率最优时间段(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式&#xff1b; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析&#xff1b; 本文收录于专栏&#xff1a;《2025华为OD真题目录…

【“星睿O6”AI PC开发套件评测】开箱+刷机+基础环境配置

开箱 很荣幸可以参与“星睿O6”AI PC开发套件评测&#xff0c;话不多说先看开箱美图&#xff0c;板子的包装还是蛮惊艳的。 基础开发环境配置 刷机 刷机参考这里的文档快速上手即可&#xff0c;笔者同时验证过使用USB和使用NVMe硬盘盒直接在硬盘上刷机&#xff0c;操作下来建…