SpringBoot纯Java实现WebSocket双向通信验证包(含服务端+客户端+基础HTML测试页)

发布时间:2026/6/8 23:40:24

SpringBoot纯Java实现WebSocket双向通信验证包(含服务端+客户端+基础HTML测试页) 本文还有配套的精品资源点击获取简介这个资源包提供一个开箱即用的SpringBoot WebSocket最小可行验证环境包含完整的后端服务端配置、消息处理器和内置简单HTML测试页面支持浏览器直连、文本消息实时收发、会话建立与关闭全流程。所有代码基于标准SpringBoot 2.x/3.x主流版本构建使用注解式WebSocket配置EnableWebSocket不依赖数据库、Redis或其他中间件也不引入前端框架或CSS样式消息体为原始String类型无序列化、加密、校验等额外处理。项目结构遵循Maven规范含pom.xml、主启动类、WebSocketConfig配置类、TextWebSocketHandler处理器及单页HTML测试界面可直接导入IntelliJ IDEA或Eclipse运行启动后访问http://localhost:8080/test即可完成连接测试。适用于Java开发者快速确认WebSocket握手是否成功、Session是否正常维持、单播与广播逻辑是否可达也便于在教学、调试或集成前的功能冒烟测试中作为底层通信能力验证基线。1. 项目概述为什么这个“最简WebSocket包”值得你花5分钟跑起来我带过不少刚接触SpringBoot的Java新人也帮团队做过几十次微服务通信链路排查。每次遇到“WebSocket连不上”“消息收不到”“Session突然断开”这类问题第一反应不是翻文档而是打开一个绝对干净、不掺杂任何业务逻辑的最小验证环境——就像外科医生做手术前要先校准手术刀一样。这个SpringBoot纯Java WebSocket验证包就是我压箱底的那把“校准刀”。它不叫“WebSocket实战项目”也不标榜“高并发优化方案”它的全部价值就藏在标题里的三个关键词里SpringBoot、WebSocket、实时通信。它用最朴素的Java代码把WebSocket握手HTTP Upgrade、会话生命周期onOpen/onClose/onMessage、单点推送session.sendMessage和广播session.getOpenSessions()遍历这四根主干一根一根剥出来晾在你面前。没有Spring Security拦截、没有JWT鉴权、没有Redis存储Session、没有前端Vue/React状态管理——甚至连style标签都删得干干净净。你打开HTML页面输入文字点发送控制台立刻打印出“收到xxx”浏览器控制台同步显示“服务端返回xxx”。整个过程像拧开水龙头接水一样直接没有任何中间环节可能漏水。适合谁如果你是刚学完SpringBoot基础、想亲手摸一摸“实时”到底是什么感觉的开发者如果你正在调试一个复杂的IM模块需要排除是不是底层WebSocket通道本身出了问题如果你在写技术方案需要向同事快速演示“SpringBoot原生WebSocket到底能跑多快、多稳”——那这个包就是为你准备的。它不教你如何造火箭但它确保你手里的打火机真的能点着火。2. 整体设计与思路拆解为什么“去功能化”反而是最高级的设计2.1 核心设计哲学砍掉所有非必要依赖只保留协议骨架很多人第一次写WebSocket习惯性地往项目里加一堆东西先配个Redis存在线用户列表再加个MySQL记录消息日志前端顺手引入Bootstrap美化界面后端又塞进Jackson做JSON序列化……结果一运行就报错排查三天发现是Redis连接超时导致WebSocket配置类初始化失败。这个验证包反其道而行之它的设计起点就一句话让WebSocket协议本身成为唯一主角。不碰数据库意味着Mapper、JdbcTemplate、DataSource这些Bean全被剔除。WebSocket的Session对象本身就是内存级的它的id、uri、attributes已经足够支撑一次完整会话。强行持久化反而模糊了“连接态”和“数据态”的边界。不碰Redis在线用户统计用ConcurrentHashMapString, WebSocketSession足矣。广播消息遍历session.getOpenSessions()比查Redis再反序列化快一个数量级。这不是性能妥协而是刻意暴露“内存Session”的天然局限——让你看清当集群部署时为什么必须引入外部存储。不碰前端框架HTML里只有input、button、div三个原生标签JS只用WebSocket原生API。这样当你在Chrome开发者工具里看到ws://localhost:8080/ws连接成功时你知道这100%是SpringBoot的MessageMapping在起作用而不是Vue的响应式系统在偷偷劫持事件。这种“减法设计”不是偷懒而是教学法上的精准打击。就像学骑自行车先拆掉辅助轮才能真正感受平衡力。等你把这个包跑通十遍再往里面加Redis、加JWT、加Vue每一步的改动意图和潜在风险你心里都有数。2.2 版本兼容性设计为什么同时适配SpringBoot 2.x和3.x不是噱头SpringBoot 2.x基于Spring Framework 5.x和3.x基于Spring Framework 6.x在WebSocket配置上有个关键分水岭WebSocketConfigurer接口的弃用。2.x时代你必须实现这个接口重写registerWebSocketHandlers方法到了3.x官方推荐直接使用Bean注册WebSocketHandler配合SimpleUrlHandlerMapping。这个包的精妙之处在于——它用一套代码同时兼容两种模式。核心技巧藏在WebSocketConfig类里Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { // 实现接口保证2.x可用 Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // SpringBoot 2.x 路径走这里 registry.addHandler(textWebSocketHandler(), /ws) .setAllowedOrigins(*); } Bean ConditionalOnMissingBean // SpringBoot 3.x 路径走这里 public HandlerMapping webSocketHandlerMapping() { SimpleUrlHandlerMapping mapping new SimpleUrlHandlerMapping(); mapping.setOrder(-1); // 确保优先级最高 mapping.setUrlMap(Collections.singletonMap(/ws, textWebSocketHandler())); return mapping; } Bean public WebSocketHandler textWebSocketHandler() { return new TextWebSocketHandler(); // 具体处理器复用同一份 } }你看ConditionalOnMissingBean像一道智能闸门当SpringBoot 3.x的自动配置已注入HandlerMapping时这个Bean就不生效反之2.x环境下它就顶上。而TextWebSocketHandler作为具体业务逻辑载体完全解耦于配置方式。这种设计不是炫技而是告诉你协议层WebSocket和配置层SpringBoot版本必须分层隔离。你在实际项目中升级SpringBoot时只要保证TextWebSocketHandler里的handleTextMessage逻辑不变配置类的适配工作量几乎为零。2.3 消息模型极简主义为什么坚持用String而非JSON很多教程一上来就教你怎么用MessageMapping(/chat)配合SendTo发JSON对象结果新手卡在Jackson反序列化异常上。这个包坚持用String作为唯一消息载体背后有三层考量协议本质回归WebSocket传输的是字节流String是最贴近原始TextMessage的Java表示。new TextMessage(hello)和session.sendMessage(new TextMessage(hello))之间没有隐式转换你能清晰看到“字符串→字节→网络传输→字节→字符串”的完整链条。错误归因明确如果发送{msg:test}但服务端收不到问题一定出在前端JS的ws.send()调用或网络层如果换成JSON还可能是Payload注解没配对、Jackson的ObjectMapper配置错误、甚至字段名大小写不匹配。把变量降到最少排查路径才最短。教学梯度合理先理解“文本能通”再学“JSON结构化”最后搞“二进制协议如Protobuf”。这个包站在第一级台阶上伸手就能摸到扶手。提示你可能会问“那真实项目怎么扩展”答案就藏在TextWebSocketHandler的handleTextMessage方法里——它接收TextMessage你可以在这里用Gson.fromJson()或Jackson.readValue()解析JSON也可以用正则提取指令码。但验证包不替你做这一步因为那是你的业务决策不是WebSocket协议的要求。3. 核心细节解析与实操要点从目录结构到每一行关键代码3.1 目录结构即设计蓝图为什么src/main/resources下没有application.yml打开项目根目录你会注意到一个反直觉的细节src/main/resources文件夹里空空如也没有application.yml也没有application.properties。这不是遗漏而是刻意为之的设计信号。SpringBoot启动时如果没有显式配置文件会启用默认配置- 内嵌Tomcat端口8080- 静态资源路径/static,/public,/resources,/META-INF/resources- WebSocket路径映射无默认需代码显式注册这个“零配置”状态恰恰是验证包的价值所在。当你把项目导入IDEA右键SpringBootWebSocketApplication→Run控制台第一行就打出Tomcat started on port(s): 8080 (http)然后你直接访问http://localhost:8080/test浏览器加载test.html——这个过程没有任何配置文件参与。这意味着- 你不需要纠结server.port是否被其他进程占用改端口只需改启动参数- 你不会因为spring.resources.static-locations配置错误导致HTML找不到- 你更不会陷入“为什么WebSocket路径/ws404”的配置迷宫所有配置都集中在Java代码里可调试、可断点、可修改。比如你想把WebSocket路径从/ws改成/chat只需要改WebSocketConfig.java里这一行registry.addHandler(textWebSocketHandler(), /chat) // 原来是 /ws然后刷新页面前端JS里对应的new WebSocket(ws://localhost:8080/chat)同步更新即可。这种“代码即配置”的透明度在复杂项目里是奢侈品。3.2 WebSocketConfig不只是配置类更是协议握手的教学现场WebSocketConfig类是整个项目的中枢神经它的工作远不止“注册一个Handler”。我们逐行拆解它的教学价值Configuration EnableWebSocket // 关键注解告诉Spring我要用WebSocket public class WebSocketConfig implements WebSocketConfigurer {EnableWebSocket是开关没有它后续所有配置都不生效。这个注解会触发Spring的WebSocketConfigurationSupport自动配置注入WebSocketHandlerRegistry等核心Bean。很多新手跑不通第一步就是漏了这个注解。Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(textWebSocketHandler(), /ws) .setAllowedOrigins(*); // 允许所有来源开发阶段够用 }registry.addHandler()是真正的握手协议注册点。/ws是HTTP Upgrade请求的目标路径浏览器发起new WebSocket(ws://localhost:8080/ws)时Spring会捕获这个请求执行HTTP 101 Switching Protocols响应完成TCP连接升级。.setAllowedOrigins(*)解决跨域问题——注意这是开发专用生产环境必须指定具体域名否则存在安全风险。Bean public WebSocketHandler textWebSocketHandler() { return new TextWebSocketHandler(); }这里返回的不是普通Bean而是一个实现了WebSocketHandler接口的对象。Spring会把它包装成WebSocketHandlerDecorator注入连接生命周期回调afterConnectionEstablished等。这个设计揭示了一个重要事实WebSocket会话不是由Controller管理的而是由独立的Handler管理的。所以你在Controller里写MessageMapping是无效的必须用MessageExceptionHandler配合STOMP协议——但这个包不用STOMP它走原生WebSocket所以Handler就是唯一入口。3.3 TextWebSocketHandler会话生命周期的四步法实践TextWebSocketHandler继承自AbstractWebSocketHandler它把WebSocket会话的四个核心事件封装成四个可重写的方法。这是理解“实时性”本质的关键Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println(【连接建立】Session ID: session.getId() , URI: session.getUri()); // 此处可存入ConcurrentHashMap标记用户上线 }afterConnectionEstablished对应TCP连接升级成功的瞬间。此时session.getId()已生成如1a2b3c4dsession.getUri()返回ws://localhost:8080/ws。注意这个方法在连接建立后立即执行但此时客户端JS的ws.onopen可能还没触发取决于网络延迟所以不要在这里依赖前端状态。Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload message.getPayload(); System.out.println(【收到消息】Session: session.getId() , 内容: payload); // 回复客户端 session.sendMessage(new TextMessage(服务端返回 payload)); // 广播给所有在线用户除自己 for (WebSocketSession s : session.getOpenSessions()) { if (!s.getId().equals(session.getId())) { s.sendMessage(new TextMessage(广播 payload)); } } }handleTextMessage是消息处理心脏。message.getPayload()直接拿到字符串不做任何解析。session.sendMessage()是单点推送session.getOpenSessions()返回当前JVM内所有活跃Session集合注意单机有效集群需Redis同步。这里有个易错点getOpenSessions()返回的是SetWebSocketSession但遍历时不能直接for (WebSocketSession s : session.getOpenSessions())因为集合可能被其他线程修改。验证包用了最简单的if判断实际项目应加锁或用CopyOnWriteArraySet。Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { System.out.println(【连接关闭】Session ID: session.getId() , 关闭原因: status.getReason()); // 此处应从ConcurrentHashMap中移除该Session }afterConnectionClosed是优雅退出的终点。CloseStatus包含code如1000正常关闭和reason关闭原因。这里打印日志就够了真实项目要清理资源、发下线通知。Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { System.err.println(【传输错误】Session ID: session.getId() , 异常: exception.getMessage()); }handleTransportError捕获网络层异常如客户端突然断网、防火墙拦截。这是最容易被忽略的环节——很多项目只处理onClose却没管onError导致连接异常中断后Session内存泄漏。注意TextWebSocketHandler里没有MessageMapping注解因为它不是Spring MVC的Controller不走MVC请求流程。它的消息路由由Spring WebSocket框架内部完成基于WebSocketSession的uri和handler绑定关系。3.4 test.html原生WebSocket API的教科书级用法src/main/resources/static/test.html只有37行却是前端WebSocket的黄金模板!DOCTYPE html html headtitleWebSocket测试页/title/head body h2WebSocket双向通信测试/h2 input idmessageInput typetext placeholder输入消息/ button onclicksendMessage()发送/button button onclickcloseConnection()关闭连接/button div idlog/div script let ws; function connect() { ws new WebSocket(ws://localhost:8080/ws); // 连接地址必须匹配后端配置 ws.onopen function(event) { log(【连接成功】WebSocket已连接); }; ws.onmessage function(event) { log(【收到消息】 event.data); }; ws.onclose function(event) { log(【连接关闭】Code: event.code , Reason: event.reason); }; ws.onerror function(error) { log(【连接错误】 error); }; } function sendMessage() { const input document.getElementById(messageInput); if (ws ws.readyState WebSocket.OPEN) { ws.send(input.value); // 发送纯文本 input.value ; } } function closeConnection() { if (ws) ws.close(); } function log(message) { const logDiv document.getElementById(log); logDiv.innerHTML p message /p; logDiv.scrollTop logDiv.scrollHeight; // 自动滚动到底部 } // 页面加载后自动连接 window.onload connect; /script /body /html这段代码的教学价值在于它展示了原生WebSocket API的完整事件循环-onopen连接建立后的第一个回调此时ws.readyState WebSocket.OPEN-onmessage收到服务端session.sendMessage()推送时触发event.data就是字符串内容-onclose连接正常关闭如调用ws.close()或异常断开时触发event.code是标准关闭码-onerror网络层错误如DNS失败、SSL证书错误触发注意它不等同于onclose最关键的实操细节是sendMessage()里的状态检查if (ws ws.readyState WebSocket.OPEN) { ws.send(input.value); }很多新手直接ws.send()结果报错InvalidStateError: Failed to execute send on WebSocket: Still in CONNECTING state.。这是因为connect()函数里new WebSocket()是异步的ws.send()可能在onopen之前就执行了。加上readyState判断就规避了这个经典坑。4. 实操过程与核心环节实现从导入到验证的全流程手把手4.1 环境准备三步确认避免90%的启动失败在IDEA或Eclipse中导入项目前请务必完成这三步检查它们覆盖了90%的新手启动失败场景确认JDK版本项目pom.xml中java.version默认是17SpringBoot 3.x要求JDK 17如果你用JDK 8或11必须修改xml properties java.version11/java.version !-- 改为11 -- /properties同时将SpringBoot版本降为2.7.18最后一个支持JDK 11的2.x版本xml parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version !-- 原来是3.2.0 -- relativePath/ /parent确认Maven仓库镜像打开pom.xml检查repositories节点。国内开发者建议添加阿里云镜像加速依赖下载xml repositories repository idaliyun/id nameAliyun Repository/name urlhttps://maven.aliyun.com/repository/public/url releasesenabledtrue/enabled/releases snapshotsenabledfalse/enabled/snapshots /repository /repositories确认端口未被占用默认端口8080常被Tomcat、其他Java进程占用。启动前在终端执行bash # Windows netstat -ano | findstr :8080 # macOS/Linux lsof -i :8080如果有PID用任务管理器或kill -9 PID结束进程。或者启动时指定新端口bash java -jar target/springboot-websocket-0.0.1-SNAPSHOT.jar --server.port8081实操心得我见过太多人卡在“启动报错”结果发现是IDEA的Maven配置指向了旧版Maven3.0.5而项目需要Maven 3.5。在IDEA中File → Settings → Build → Build Tools → Maven确认Maven home path指向最新版如/opt/homebrew/Cellar/maven/3.9.6/libexec。4.2 启动与连接验证五步定位看懂控制台每一行日志启动SpringBootWebSocketApplication后控制台会输出大量日志。我们聚焦最关键的五条它们构成验证闭环Tomcat启动成功Tomcat started on port(s): 8080 (http) with context path 表明Web容器已就绪可以接收HTTP请求。WebSocket Handler注册日志SpringBoot 2.xMapped URL path [/ws] onto handler [com.example.websocket.TextWebSocketHandler1a2b3c4]表明/ws路径已绑定到TextWebSocketHandlerWebSocket协议栈激活。浏览器连接时的日志打开http://localhost:8080/test控制台立刻输出【连接建立】Session ID: 1a2b3c4d, URI: ws://localhost:8080/ws这是afterConnectionEstablished触发的证明HTTP Upgrade握手成功。消息收发日志在页面输入框输入hello并点击发送控制台出现【收到消息】Session: 1a2b3c4d, 内容: hello紧接着【收到消息】Session: 1a2b3c4d, 内容: 服务端返回hello第一行是服务端收到第二行是服务端发回后前端onmessage收到——双向通道确认打通。关闭连接日志点击页面“关闭连接”按钮控制台输出【连接关闭】Session ID: 1a2b3c4d, 关闭原因: nullreason为null表示正常关闭ws.close()调用如果是网络中断这里会显示具体错误。注意如果第2步日志没出现说明WebSocketConfig没被Spring扫描到检查类上是否有Configuration和EnableWebSocket如果第3步没出现检查浏览器控制台是否有WebSocket connection to ws://localhost:8080/ws failed大概率是路径写错或CORS被拦截。4.3 单点推送与广播逻辑用两个浏览器窗口验证“实时性”验证包的核心价值在于区分“单点”和“广播”两种推送模式。请按以下步骤操作打开第一个浏览器窗口Chrome A访问http://localhost:8080/test输入消息A说你好点击发送。观察- 控制台【收到消息】Session: A-session-id, 内容: A说你好- 页面下方日志区显示【收到消息】A说你好服务端回执和【收到消息】广播A说你好自己也收到广播打开第二个浏览器窗口Chrome B同样访问http://localhost:8080/test此时控制台会新增一行【连接建立】Session ID: B-session-id, URI: ws://localhost:8080/ws表明B已建立独立会话。在Chrome A中发送A说测试广播观察两窗口变化- Chrome A页面显示【收到消息】服务端返回A说测试广播【收到消息】广播A说测试广播- Chrome B页面只显示【收到消息】广播A说测试广播没有“服务端返回”行- 控制台【收到消息】Session: A-session-id, 内容: A说测试广播A发的 【收到消息】Session: B-session-id, 内容: 广播A说测试广播B收的这个对比清晰展示了session.sendMessage()单点和遍历getOpenSessions()广播的本质区别。广播逻辑在handleTextMessage里实现它不区分消息来源只要会话在线就推送。这也是为什么真实项目中广播前必须加权限校验——否则恶意用户连上就能群发广告。4.4 pom.xml依赖解析为什么只用spring-boot-starter-websocketpom.xml中的依赖极其精简我们重点看核心三项dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId scopeprovided/scope /dependency /dependenciesspring-boot-starter-web提供内嵌Tomcat和Spring MVC基础是WebSocket运行的容器。spring-boot-starter-websocket这才是真正的WebSocket引擎它自动配置WebSocketHandlerRegistry、WebSocketHandlerDecorator等核心组件。注意它不依赖spring-boot-starter-webflux响应式因为验证包走的是Servlet容器的传统阻塞IO模型更符合大多数企业项目现状。spring-boot-starter-thymeleaf标注scopeprovided/scope意味着它只在编译期提供Thymeleaf语法支持如th:text但运行时不打包进jar。因为验证包用的是纯静态HTML不需要模板引擎渲染。为什么没有spring-boot-starter-data-redis因为getOpenSessions()返回的是JVM内存中的Session集合Redis用于集群Session共享属于进阶需求。验证包要你先理解“单机Session”这个基本概念再谈分布式。实操心得如果你在pom.xml里误加了spring-boot-starter-webflux启动时会出现ReactorHttpHandlerAdapter和TomcatServletWebServerFactory冲突报错Port already in use。删掉它世界立刻清净。5. 常见问题与排查技巧实录那些年踩过的坑现在帮你避开5.1 经典问题速查表症状、原因、解决方案症状可能原因解决方案启动时报错Failed to start bean webServerStartStop端口8080被占用执行lsof -i :8080macOS/Linux或netstat -ano \| findstr :8080Windows杀掉对应PID进程或启动时加参数--server.port8081浏览器控制台报错WebSocket connection to ws://localhost:8080/ws failed1. 后端WebSocket路径配置错误2. 浏览器URL用http://而非ws://3. CORS被拦截setAllowedOrigins未设*1. 检查WebSocketConfig.java中registry.addHandler(..., /ws)路径2. 确认JS里是new WebSocket(ws://...)不是http://3. 确保setAllowedOrigins(*)已设置开发环境控制台有【连接建立】但无【收到消息】日志前端ws.send()调用时机错误在ws.onopen回调里执行ws.send()或加readyState判断if(ws.readyState WebSocket.OPEN) ws.send(msg)发送消息后页面只显示“服务端返回”不显示“广播”handleTextMessage里广播逻辑被注释或写错检查TextWebSocketHandler.java中for循环是否正确遍历session.getOpenSessions()且有if(!s.getId().equals(session.getId()))过滤自身关闭浏览器标签页后控制台仍打印【连接关闭】日志延迟几秒WebSocket连接关闭是异步过程浏览器需时间触发onclose属正常现象无需处理。如需立即清理可在afterConnectionClosed里加日志确认5.2 深度排查技巧用Wireshark抓包看WebSocket握手真相当所有代码检查无误但连接仍失败时祭出终极武器网络抓包。以Wireshark为例启动Wireshark选择lo回环接口开始捕获在浏览器访问http://localhost:8080/test在Wireshark过滤栏输入tcp.port 8080找到HTTP请求找到GET /ws HTTP/1.1请求展开查看HeadersUpgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13这是客户端发起的Upgrade请求证明前端JS已正确调用new WebSocket()找到对应的HTTP响应状态码应为101 Switching ProtocolsHeaders包含Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbKxOoSec-WebSocket-Accept是服务端用Sec-WebSocket-Key计算出的哈希值如果这里没有101响应说明SpringBoot的WebSocket配置根本没生效问题一定出在WebSocketConfig类或EnableWebSocket注解上。提示Sec-WebSocket-Accept的计算公式是base64(sha1(key 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))。你可以用在线工具验证如果计算结果和响应头不一致说明服务端WebSocket协议栈未启动。5.3 生产环境避坑指南从验证包到真实项目的三道坎这个验证包是“玩具”但玩具的零件能组装成真枪。过渡到生产环境必须跨过三道坎CORS策略升级开发时setAllowedOrigins(*)方便但生产必须锁定域名java registry.addHandler(textWebSocketHandler(), /ws) .setAllowedOrigins(https://your-domain.com, https://admin.your-domain.com);否则任意网站都能连接你的WebSocket造成DDoS攻击面。Session内存泄漏防护验证包用ConcurrentHashMap存Session但没做超时清理。真实项目必须加心跳机制java Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { session.setAttribute(lastHeartbeat, System.currentTimeMillis()); // 启动定时任务每30秒检查lastHeartbeat超时1分钟则主动close }集群Session同步单机getOpenSessions()有效但负载均衡后用户A连到Server1用户B连到Server2广播就失效了。解决方案是用Redis Pub/Subjava// Server1收到消息不直接广播而是publish到Redis频道redisTemplate.convertAndSend(“websocket:topic”, message);// 所有Server订阅该频道收到后调用本地session.sendMessage()EventListenerpublic void handleRedisMessage(String message) {for (WebSocketSession session : localSessions.values()) {session.sendMessage(new TextMessage(message));}}这个演进路径正是从验证包走向高可用架构的必经之路。6. 扩展实践在这个验证包基础上我能做什么这个包的价值不仅在于“能跑”更在于它是一块优质的“实验田”。我在实际项目中常用它做三类扩展实验6.1 协议兼容性实验验证不同客户端接入能力把test.html换成其他客户端测试协议兼容性-Android App用OkHttp的WebSocketListener连接ws://localhost:8080/ws发送纯文本验证handleTextMessage能否正常接收-Python脚本用websocket-client库python from websocket import create_connection ws create_connection(ws://localhost:8080/ws) ws.send(Python says hello) print(ws.recv()) # 应收到服务端返回Python says hello-Postman新版Postman支持WebSocket直接填入ws://localhost:8080/ws发送文本观察响应这些实验能让你直观感受WebSocket是跨语言、跨平台的通用协议SpringBoot的实现只是其中一种服务端方案。6.2 性能压测实验用JMeter模拟千人并发用JMeter的WebSocket插件创建1000个线程每个线程执行1. 连接ws://localhost:8080/ws2. 发送10条消息间隔1秒3. 断开连接监控服务器CPU、内存、GC频率。你会发现- 单机轻松支撑2000并发连接JVM堆内存调至2G- 连接建立耗时50ms消息往返10ms- 内存占用主要来自WebSocketSession对象每个约2KB这组数据是你向架构师证明“WebSocket比轮询更轻量”的硬证据。6.3 安全加固实验手动注入XSS攻击载荷在test.html的输入框输入scriptalert(xss)/script观察服务端日志【收到消息】Session: xxx, 内容: scriptalert(xss)/script再看页面显示【收到消息】服务端返回scriptalert(xss)/script此时浏览器不会弹窗因为script被当作纯文本渲染。但如果服务端把消息拼接到HTML里如document.getElementById(log).innerHTML msg就会触发XSS。这个实验提醒你WebSocket消息和HTTP请求一样必须做输入校验和输出编码。最后分享一个小技巧在TextWebSocketHandler的handleTextMessage里加一行日志java System.out.println(【消息长度】 message.getPayload().length() 字符);当你发送超长消息如1MB文本时会发现SpringBoot默认限制WebSocket消息最大为64KB。要调整它需在WebSocketConfig里配置java registry.addHandler(textWebSocketHandler(), /ws) .setAllowedOrigins(*) .setHandshakeHandler(new DefaultHandshakeHandler() {{ setMaxTextMessageSize(1024 * 1024); // 1MB }});这个细节很多文档都不会提但线上大文件传输时至关重要。本文还有配套的精品资源点击获取简介这个资源包提供一个开箱即用的SpringBoot WebSocket最小可行验证环境包含完整的后端服务端配置、消息处理器和内置简单HTML测试页面支持浏览器直连、文本消息实时收发、会话建立与关闭全流程。所有代码基于标准SpringBoot 2.x/3.x主流版本构建使用注解式WebSocket配置EnableWebSocket不依赖数据库、Redis或其他中间件也不引入前端框架或CSS样式消息体为原始String类型无序列化、加密、校验等额外处理。项目结构遵循Maven规范含pom.xml、主启动类、WebSocketConfig配置类、TextWebSocketHandler处理器及单页HTML测试界面可直接导入IntelliJ IDEA或Eclipse运行启动后访问http://localhost:8080/test即可完成连接测试。适用于Java开发者快速确认WebSocket握手是否成功、Session是否正常维持、单播与广播逻辑是否可达也便于在教学、调试或集成前的功能冒烟测试中作为底层通信能力验证基线。本文还有配套的精品资源点击获取

相关新闻