在 Spring Boot 中实现 WebSockets
什么是 WebSockets?
WebSockets 是一种基于 TCP 的全双工通信协议,允许客户端和服务器之间建立持久的双向连接,用于实时数据交换。相较于传统的 HTTP 请求-响应模型,WebSockets 提供了低延迟、高效率的通信方式,特别适合需要实时更新的应用场景,如聊天应用、实时通知、在线游戏和股票价格更新。
核心特点
- 全双工通信:客户端和服务器可同时发送和接收数据。
- 持久连接:一次握手后,连接保持开放,减少重复建立连接的开销。
- 低延迟:适合实时性要求高的场景。
- 轻量协议:基于 HTTP 协议升级(通过
Upgrade
头),数据帧开销小。 - 跨平台支持:现代浏览器和服务器均支持 WebSockets。
工作原理
- 握手:客户端通过 HTTP 发送 WebSocket 握手请求(
GET /ws HTTP/1.1
带Upgrade: websocket
头),服务器响应101 Switching Protocols
。 - 连接建立:双方建立 WebSocket 连接,使用
ws://
或wss://
(加密)协议。 - 数据交换:通过文本或二进制帧传输数据,连接保持开放直到一方关闭。
- 关闭:发送关闭帧,断开连接。
与 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. 环境搭建
-
添加依赖(
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>
-
配置
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。
-
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} }
-
消息控制器:
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; // 广播消息} }
-
消息实体:
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; } }
-
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>
-
控制器:
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";} }
-
运行验证:
- 启动应用:
mvn spring-boot:run
。 - 访问
http://localhost:8081/chat
,打开多个浏览器窗口。 - 输入用户名和消息,发送后所有窗口实时显示消息。
- 启动应用:
3. 与先前查询集成
结合你的查询(分页、Swagger、ActiveMQ、Spring Profiles、Spring Security、Spring Batch、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、异常处理):
-
分页与排序:
- 实时显示分页用户数据:
@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 }
- 实时显示分页用户数据:
-
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"); }
- 文档化 REST API,非 WebSocket:
-
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;} }
- 记录聊天消息:
-
Spring Profiles:
- 配置开发/生产环境:
# application-dev.yml spring:freemarker:cache: false logging:level:root: DEBUG
# application-prod.yml spring:freemarker:cache: true
- 配置开发/生产环境:
-
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();} }
- 保护 WebSocket 连接:
-
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();} }
- 批量推送用户数据:
-
FreeMarker:
- 已使用 FreeMarker 渲染聊天页面。
-
热加载:
- 启用 DevTools:
spring:devtools:restart:enabled: true
- 启用 DevTools:
-
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();}} }
- 清理 WebSocket 上下文:
-
Actuator 安全性:
- 保护
/actuator/**
,允许/actuator/health
。
- 保护
-
CSRF:
- WebSocket 端点禁用 CSRF(见
SecurityConfig
)。
- WebSocket 端点禁用 CSRF(见
-
异常处理:
- 处理 WebSocket 异常:
@ControllerAdvice public class WebSocketExceptionHandler {@ExceptionHandler(MessagingException.class)public void handleMessagingException(MessagingException ex, SimpMessageHeaderAccessor headerAccessor) {headerAccessor.getSessionAttributes().put("error", ex.getMessage());} }
- 处理 WebSocket 异常:
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");// 测试消息发送
}
常见问题
-
连接失败:
- 问题:WebSocket 握手失败。
- 解决:检查端点路径,禁用防火墙。
-
消息丢失:
- 问题:客户端未收到消息。
- 解决:确认订阅
/topic/messages
。
-
ThreadLocal 泄漏:
- 问题:
/actuator/threaddump
显示泄漏。 - 解决:清理 ThreadLocal。
- 问题:
总结
WebSockets 提供实时双向通信,Spring Boot 通过 STOMP 简化实现。示例展示了聊天应用及与分页、Swagger、ActiveMQ 等集成。针对你的查询(ThreadLocal、Actuator、热加载、CSRF),通过清理、Security 和 DevTools 解决。
相关文章:
在 Spring Boot 中实现 WebSockets
什么是 WebSockets? WebSockets 是一种基于 TCP 的全双工通信协议,允许客户端和服务器之间建立持久的双向连接,用于实时数据交换。相较于传统的 HTTP 请求-响应模型,WebSockets 提供了低延迟、高效率的通信方式,特别适…...

stone 3d v3.3.0版本发布,含时间线和连接器等新功能
1.新加了时间线(timeline)编辑器,可以类似blender一样给对象制作动画 2.新加了度量(metrics)系统,通过scene对象检测器中的useMetrics属性来启用或禁用,启用时所选物体将显示三维度量数据 新加了…...

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

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

C#初级知识总结
一、什么是CIL 1.CIL(Common Intermidate Language)是指.Net的公共中间语言,它是一种编程语言。 .Net框架的各种语言在编译时都会编译成同一种中间语言(CIL),之后程序运行的时候CIL会被JIT(Just In Time)转换为二进制语言…...
使用 AI Agent 改善师生互动的设计文档
使用 AI Agent 改善师生互动的设计文档 一、引言 1.1 研究背景 当前教育领域的师生互动存在诸多挑战,如教师负担过重、学生个体差异大导致难以满足所有人的需求,以及信息传递延迟等问题。引入AI-Agent能够有效缓解这些问题,通过自动化手段协…...

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

16:00开始面试,16:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到4月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
深度解析云计算:概念、优势与分类全览
以下是对云计算概念、优点和分类更详细的介绍: 一、云计算的概念 云计算是一种通过互联网提供计算服务的模式,它基于虚拟化、分布式计算、网络存储等一系列先进技术,将计算资源进行整合和管理,形成一个庞大的资源池。这些资源包…...

私钥连接服务器(已经有服务器私钥
前言:假设我们已经有了服务器的私钥,我们怎么配置呢? 下面我会从vsc的配置角度来写 ✅ 步骤一:准备工作 安装 VS Code(如果还没装) 👉 https://code.visualstudio.com/ 安装插件:Re…...

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

外观模式:简化复杂系统接口的设计模式
外观模式:简化复杂系统接口的设计模式 一、模式核心:为复杂子系统提供统一简单接口 当一个系统由多个复杂子系统组成时(如电商系统中的支付、物流、库存模块),客户端直接调用子系统会导致依赖关系复杂、代码难以维护…...
vue3项目中eslint.config.ts配置rules
vue3项目中eslint.config.ts配置rules 1. 使用npm create vuelatest创建vue项目 默认的eslint.config.ts如下 import { globalIgnores } from eslint/config import { defineConfigWithVueTs, vueTsConfigs } from vue/eslint-config-typescript import pluginVue from esli…...

uniapp-商城-36-shop 购物车 选好了 进行订单确认2 支付方式颜色变化和颜色滤镜filter
颜色滤镜,在好多网页都这样使用,滤掉彩色,显示黑白,这在一些关键的日子中都这样使用。 1、依然回到订单确认页面 看到支付的颜色了嘛? <view class"payType"><view class"box" :class&q…...

Vue3 上传后的文件智能预览(实战体会)
目录 前言1. Demo12. Demo2 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 爬虫神器,无代码爬取,就来:bright.cn 此处的基本知识涉及较少,主要以Demo的形式供大…...
铃木一郎女儿是奥运会选手吗·棒球1号位
铃木一朗(Ichiro Suzuki) 铃木一朗职业生涯时间线 1973年出生于日本爱知县名古屋市。1992年以选秀第四顺位加入日本职棒(NPB)欧力士蓝浪队,开启职业棒球生涯。 1994-2000年 连续7年获得NPB太平洋联盟打击王ÿ…...
PyTorch与CUDA的关系
文章目录 前言一、如何查看PyTorch和torchvision的版本1.1 查看PyTorch版本1.2 查看torchvision版本二、如何确认PyTorch和torchvision是否支持CUDA加速2.1 检查PyTorch是否支持CUDA2.2 查看当前可用的GPU设备2.3 检查torchvision是否支持CUDA三、CUDA版本的秘密:为什么PyTorc…...

CCE13.【C++ Cont】练习题组13 静态链表专题
目录 1.B3630 排队顺序 题目 分析 代码 提交结果 2.B3631 单向链表 题目 分析 前置知识:map数组加快访问速度(简单的哈希表优化) 使用map数组的重要提醒 代码 提交结果 3.★P1160 队列安排 题目 分析 方法1:带头不循环双向链表的设计 方法2:带头循环的双向链表…...
【Mybatis】MyBatisPlus的saveBatch真的是批量插入吗?深度解析与性能优化
前言 在使用MyBatis-Plus进行批量数据插入时,许多开发者会发现:即使调用saveBatch方法,数据库仍会产生大量INSERT语句。本文将深入源码揭示背后的真相,并提供3种性能优化方案,让你的批量插入速度提升10倍!…...

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

R7周:糖尿病预测模型优化探索
🍨 本文为🔗365天深度学习训练营中的学习记录博客 🍖 原作者:K同学啊 一、数据预处理 1.设置GPU import torch.nn.functional as F import torch.nn as nn import torch, torchvisiondevice torch.device("cuda"…...

线程怎么创建?Java 四种方式一网打尽
🚀 Java 中线程的 4 种创建方式详解 创建方式实现方式是否推荐场景说明1. 继承 Thread 类class MyThread extends Thread❌ 不推荐简单学习、单线程场景2. 实现 Runnable 接口class MyRunnable implements Runnable✅ 推荐更适合多线程共享资源3. 实现 Callable 接…...
前端如何连接tcp 服务,接收数据
在传统的浏览器前端环境中,由于浏览器的同源策略和安全限制,无法直接建立 TCP 连接。不过,可以通过 WebSocket 或者使用 WebRTC 来间接实现与 TCP 服务的通信,另外在 Node.js 环境中可以直接使用 net 模块建立 TCP 连接。下面分别…...

STM32之DHT11温湿度传感器---附代码
DHT11简介 DHT11的供电电压为 3-5.5V。 传感器上电后,要等待 1s 以越过不稳定状态在此期间无需发送任何指令。 电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。 DATA 用于微处理器与DHT11之间…...

工业相机——镜头篇【机器视觉,图像采集系统,成像原理,光学系统,成像光路,镜头光圈,镜头景深,远心镜头,分辨率,MTF曲线,焦距计算 ,子午弧矢】
文章目录 1 机器视觉,图像采集系统2 相机镜头,属于一种光学系统3 常规镜头 成像光路4 镜头光圈5 镜头的景深6 远心镜头 及 成像原理7 远心镜头种类 及 应用场景8 镜头分辨率10 镜头的对比度11 镜头的MTF曲线12 镜头的焦距 计算13 子午弧矢 图解 反差 工业…...
如何在Spring Boot中禁用Actuator端点安全性
在 Spring Boot 应用中,Spring Boot Actuator 提供了一系列用于监控和管理应用的端点(如 /actuator/health、/actuator/metrics),这些端点默认可能受到 Spring Security 的保护,要求身份验证或授权。然而,在…...
第48讲:空间大数据与智慧农业——时空大数据分析与农业物联网的融合实践
目录 🧠 一、什么是空间大数据? 📡 二、农业物联网:数据采集的神经末梢 🔁 三、融合应用:空间大数据 + 农业IoT = 决策大脑 1. 精准灌溉管理 2. 时空病虫害预警 3. 农业碳监测与生态评估 💡 四、技术实践案例:农田干旱预警系统 📌 场景设定: 🛠 数据…...

openwrt查询网关的命令
方法一:route -n 方法二:ip route show...

华为OD机试真题——查找接口成功率最优时间段(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录…...
SiamMask原理详解:从SiamFC到SiamRPN++,再到多任务分支设计
SiamMask原理详解:从SiamFC到SiamRPN,再到多任务分支设计 一、引言二、SiamFC:目标跟踪的奠基者1. SiamFC的结构2. SiamFC的局限性 三、SiamRPN:引入Anchor机制的改进1. SiamRPN的创新2. SiamRPN的进一步优化 四、SiamMask&#x…...