一、什么是 WebSocket?

  • 定义:WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。

  • 作用:实现客户端(浏览器)和服务器之间的实时、双向通信。

  • 优势

    • 连接保持,通信实时性强(不像 HTTP 需要每次请求都建立连接)。

    • 节省带宽,减少延迟。

    • 适合聊天室、实时推送、游戏、股票行情、物联网等场景。


二、WebSocket 工作原理简述

  1. 客户端发起 HTTP 请求(带有 Upgrade: websocket 头)请求升级协议。

  2. 服务器响应协议升级,建立 WebSocket 连接。

  3. 建立连接后,客户端和服务器可以随时互相发送消息,连接保持,直到关闭。

  4. 连接关闭后,双方不能再通信。


三、Java 原生 WebSocket 使用示例

Java 标准库中 javax.websocket 提供了 WebSocket 支持。下面示例是一个简单的服务端:

1. 添加依赖(以 Maven 为例)

<!-- 只在Java EE容器或者支持Java WebSocket API的容器中需要 -->
<dependency><groupId>javax.websocket</groupId><artifactId>javax.websocket-api</artifactId><version>1.1</version>
</dependency>

2. 编写WebSocketConfig配置类

Spring Boot 默认不支持直接扫描 @ServerEndpoint,需要做个配置类注册它:

@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

这个 ServerEndpointExporter 负责注册所有带 @ServerEndpoint 注解的 WebSocket 类。

(1) @ServerEndpoint 注解的类和 Spring 容器
  • 使用标准 Java EE javax.websocket API 的 @ServerEndpoint 注解时,WebSocket 服务端点是由 Java EE 容器(如 Tomcat、Jetty)扫描并管理的,不依赖 Spring 容器。

  • 但是,Spring Boot 本身不会自动扫描和管理@ServerEndpoint 注解的类。


(2) 为什么需要 ServerEndpointExporter
  • ServerEndpointExporter 是 Spring Boot WebSocket 模块提供的一个 bean,作用是:

    • 让 Spring Boot 容器扫描并注册所有用 @ServerEndpoint 注解的类。

    • 把这些端点交给底层的 WebSocket 容器(Tomcat 等)管理。


(3) 什么时候需要 ServerEndpointExporter
  • 如果你用的是 Spring Boot 嵌入式 Tomcat 或其他容器,且你的 WebSocket 端点是用标准的 @ServerEndpoint 注解实现的,通常需要配置这个 bean

3. 编写 WebSocket 服务器端

@ServerEndpoint("/myWs")
@Component
@Slf4j
public class WsServerEndpont {static Map<String,Session> sessionMap = new ConcurrentHashMap<>();//连接建立时执行的操作@OnOpenpublic void onOpen(Session session){sessionMap.put(session.getId(),session);log.info("websocket is open");}//收到了客户端消息执行的操作@OnMessagepublic String onMessage(String text){log.info("收到了一条消息:"+text);return "已收到你的消息";}//连接关闭的时候执行的操作@OnClosepublic void onClose(Session session){sessionMap.remove(session.getId());log.info("websocket is close");}//每2s发送给客户端心跳消息@Scheduled(fixedRate = 2000)public void sendMsg() throws IOException {for(String key:sessionMap.keySet()){sessionMap.get(key).getBasicRemote().sendText("心跳");}}
}

3. 部署运行

  • 需要在支持 Java EE WebSocket 的容器中(如 Tomcat 8+、Jetty、Glassfish)部署。

  • 客户端可用浏览器或工具连接:ws://localhost:8080/your-app/websocket


四、Spring Boot 中使用 WebSocket(实现多人聊天)

        Spring Boot 提供了非常方便的 WebSocket 支持,通常结合 STOMP 协议和 SockJS 来实现消息的订阅和广播。

1. 添加依赖(Maven)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Spring Boot 的依赖管理机制(依赖版本继承)

  • Spring Boot 使用了 “依赖管理(Dependency Management)”,这是它的核心特性之一。

  • 你只需在你的项目中声明 spring-boot-starter-parent 或通过 spring-boot-dependencies 管理依赖版本,Spring Boot 会自动帮你管理和统一版本号。

2. 配置 MyWsConfig

// WebSocket配置类,注册WebSocket处理器和拦截器
@Configuration
@EnableWebSocket  // 启用WebSocket支持
public class MyWsConfig implements WebSocketConfigurer {@ResourceMyWsHandler myWsHandler;  // 自定义的WebSocket处理器@ResourceMyWsInterceptor myWsInterceptor; // 自定义的握手拦截器@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器,路径为/myWs1// 添加握手拦截器,允许所有源跨域请求(*)registry.addHandler(myWsHandler, "/myWs1")        // 注册WebSocket处理器及路径.addInterceptors(myWsInterceptor)          // 添加握手阶段的拦截器,做一些额外处理.setAllowedOrigins("*");                    // 允许所有域发起跨域连接请求}
}
2.1 代码讲解:
(1) addHandler(myWsHandler, "/myWs1")
  • 作用:给 WebSocket 服务器注册一个处理器(Handler),并指定客户端连接时使用的 URL 路径。

  • myWsHandler 是你自定义的 WebSocket 业务处理类(继承自 WebSocketHandlerAbstractWebSocketHandler)。

  • "/myWs1" 是 WebSocket 连接的访问路径,客户端通过 ws://服务器地址/myWs1 来建立 WebSocket 连接。

总结:这句相当于告诉服务器,“当客户端请求 /myWs1 路径时,交给 myWsHandler 来处理这次 WebSocket 连接和消息。”


(2)addInterceptors(myWsInterceptor)
  • 作用:给这个 WebSocket 处理器绑定一个或多个拦截器(HandshakeInterceptor),用来拦截 WebSocket 握手阶段的请求。

  • 握手阶段是 WebSocket 建立连接的第一步,类似 HTTP 请求升级。

  • 你可以在拦截器里做一些额外操作,比如:

    • 记录日志

    • 权限校验

    • 传递用户信息到 WebSocket Session

    • 修改握手请求和响应

  • 你的 myWsInterceptor 继承自 HttpSessionHandshakeInterceptor,默认支持把 HTTP Session 关联到 WebSocket Session。

总结:这句是告诉服务器,“在建立 WebSocket 连接握手时,执行 myWsInterceptor 中的逻辑。”


(3)setAllowedOrigins("*")
  • 作用:设置允许连接的客户端来源(Origin),即支持跨域连接的源。

  • 这里 "*" 表示允许所有源都能连接你的 WebSocket 服务。

  • Origin 是浏览器在 WebSocket 握手请求头里自动带上的,服务器根据它判断是否允许该请求。

  • 注意:生产环境一般不要用 "*",建议指定可信的域名,如 setAllowedOrigins("https://example.com")

总结:这句是告诉服务器,“允许哪些来源的网页可以发起 WebSocket 连接请求”。

3. 配置 MyWsHandler

@Slf4j
@Component
public class MyWsHandler extends AbstractWebSocketHandler {// 存放所有连接的客户端会话,线程安全的Mapprivate static Map<String, SessionBean> sessionBeanMap;// 用于生成客户端唯一ID的原子计数器private static AtomicInteger clientIdMaker;// 用于存放群聊消息的缓冲区private static StringBuffer stringBuffer;static {sessionBeanMap = new ConcurrentHashMap<>();clientIdMaker = new AtomicInteger(0);stringBuffer = new StringBuffer();}/*** 连接建立成功时调用//相当于上面原生的onOpe******************************/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {super.afterConnectionEstablished(session);// 新建SessionBean,给该连接分配唯一clientIdSessionBean sessionBean = new SessionBean(session, clientIdMaker.getAndIncrement());// 放入sessionMap管理sessionBeanMap.put(session.getId(), sessionBean);log.info(sessionBean.getClientId() + "建立了连接");// 群聊消息中记录进入群聊通知stringBuffer.append(sessionBean.getClientId() + "进入了群聊<br/>");// 给所有客户端发送当前群聊消息sendMessage(sessionBeanMap);}/*** 收到客户端消息时调用//相当于上面原生的onMessage*********************************/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {super.handleTextMessage(session, message);// 记录日志,打印客户端发送的消息log.info(sessionBeanMap.get(session.getId()).getClientId() + ":" + message.getPayload());// 将消息追加到群聊缓冲区stringBuffer.append(sessionBeanMap.get(session.getId()).getClientId() + ":" + message.getPayload() + "<br/>");// 广播给所有客户端sendMessage(sessionBeanMap);}/*** 传输发生异常时调用*/@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {super.handleTransportError(session, exception);// 如果会话还开着,先关闭if (session.isOpen()) {session.close();}// 移除该会话sessionBeanMap.remove(session.getId());}/*** 连接关闭时调用 //相当于上面原生的onClose*****************************/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {super.afterConnectionClosed(session, status);// 获取即将关闭连接的clientIdint clientId = sessionBeanMap.get(session.getId()).getClientId();// 移除关闭的会话sessionBeanMap.remove(session.getId());log.info(clientId + "关闭了连接");// 群聊消息中记录退出通知stringBuffer.append(clientId + "退出了群聊<br/>");// 广播给所有客户端sendMessage(sessionBeanMap);}/*** 给所有客户端发送消息*/public void sendMessage(Map<String, SessionBean> sessionBeanMap) {for (String key : sessionBeanMap.keySet()) {try {// 发送群聊缓冲区里的消息给每个客户端sessionBeanMap.get(key).getWebSocketSession().sendMessage(new TextMessage(stringBuffer.toString()));} catch (IOException e) {// 日志记录异常log.error(e.getMessage());}}}
}

4. 配置 MyWsInterceptor

@Slf4j
@Component
public class MyWsInterceptor extends HttpSessionHandshakeInterceptor {/*** 握手之前调用,可用于记录日志或做权限校验*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {log.info(request.getRemoteAddress().toString() + "开始握手");return super.beforeHandshake(request, response, wsHandler, attributes);}/*** 握手完成调用*/@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception ex) {log.info(request.getRemoteAddress().toString() + "完成握手");super.afterHandshake(request, response, wsHandler, ex);}
}

5. 配置 SessionBean


// 简单的会话封装类,保存WebSocketSession和客户端唯一ID
@AllArgsConstructor
@Data
public class SessionBean {private WebSocketSession webSocketSession;private Integer clientId;
}

6. 启动类WsDemoApplication 

// 启动类,开启定时任务支持
@EnableScheduling
@SpringBootApplication
public class WsDemoApplication {public static void main(String[] args) {SpringApplication.run(WsDemoApplication.class, args);}
}

7. 前端示例(HTML + JavaScript)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ws client</title>
</head>
<body>
<p style="border:1px solid black;width: 600px;height: 500px" id="talkMsg"></p>
<input id="message" /><button id="sendBtn" onclick="sendMsg()">发送</button>
</body>
<script>let ws = new WebSocket("ws://localhost:8080/myWs1")// ws.onopen=function () {// }ws.onmessage=function (message) {document.getElementById("talkMsg").innerHTML = message.data}function sendMsg() {ws.send(document.getElementById("message").value)document.getElementById("message").value=""}
</script>
</html>

五、小结

特点Java 原生 WebSocketSpring Boot WebSocket + STOMP
适用场景简单直接的 WebSocket 应用需要消息订阅/广播、复杂消息路由的应用
配置复杂度低(容器支持即可)需要配置消息代理,依赖更多
功能支持基础双向通信支持订阅、广播、分组、消息转换
前端开发支持需自行实现协议使用 STOMP.js 等库简化开发

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【STM32 LWIP配置】STM32H723ZG + Ethernet +LWIP 配置 cubemx

STM32H723ZG LAN8742 Ethernet LWIP 配置 cubemx &#x1f31e;这边记录一下这块mcu 配置以太网的过程&#xff0c;IDE是KEIL MDK&#xff0c;其实就是在下面多次提到的blog的基础上 在scatter file进行配置 首先&#xff0c;如果想要简单一点 直接去cubemx 那边获取相关的例…

EI检索-学术会议 | 人工智能、虚拟现实、可视化

第五届人工智能、虚拟现实与可视化国际学术会议&#xff08;AIVRV 2025&#xff09;定于2025年9月5-7日在中国 成都召开。人工智能正驱动各行业智能化转型&#xff0c;提升效率与质量&#xff1b;虚拟现实技术以其沉浸感重塑教育、娱乐、医疗等领域体验&#xff1b;可视化技术…

力扣(H指数)

一、题目分析 &#xff08;一&#xff09;问题描述 给定一个整数数组 citations&#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。我们需要计算并返回该研究者的 H 指数。根据维基百科定义&#xff1a;H 指数代表“高引用次数”&#xff0c;一名科研人员的…

标准io(1)

标准I/O基础概念标准I/O&#xff08;Standard Input/Output&#xff09;是C语言提供的一组高级文件操作函数&#xff0c;位于<stdio.h>头文件中。与低级I/O&#xff08;如Unix的系统调用read/write&#xff09;相比&#xff0c;标准I/O引入了缓冲机制&#xff0c;能显著提…

线性代数1000题学习笔记

1000题线代基础第一章1-101000题线代基础第二章1-171000题线代基础第三章1-11

LeetCode算法日记 - Day 8: 串联所有单词的子串、最小覆盖子串

目录 1.串联所有单词的子串 1.2 解法 1.3 代码实现 2. 最小覆盖子串 2.1 题目解析 2.2 解法 2.3 代码实现 1.串联所有单词的子串 30. 串联所有单词的子串 - 力扣&#xff08;LeetCode&#xff09; 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度…

linux实战:基于Ubuntu的专业相机

核心组件就是QTimerOpenCV的组合方案摄像头启停控制用QPushButton实现&#xff0c;帧显示必须用QLabel而不能用普通控件&#xff0c;视频流刷新用QTimer比多线程更简单想快速实现摄像头控制功能&#xff0c;核心组件就是QTimerOpenCV的组合方案。摄像头启停控制用QPushButton实…

《深度剖析前端框架中错误边界:异常处理的基石与进阶》

错误边界作为一种特殊的组件机制&#xff0c;正悄然重塑着应用应对异常的底层逻辑。它并非简单的代码片段组合&#xff0c;而是一套贯穿组件生命周期的防护体系&#xff0c;其核心价值在于将局部错误的影响牢牢锁定在可控范围内&#xff0c;避免整个应用陷入不可挽回的崩溃状态…

6GB显存玩转SD微调!LoRA-scripts本地部署教程,一键炼出专属AI画师

一、介绍LoRA-scripts&#xff08;又名 SD-Trainer&#xff09;&#xff0c;是一个专为训练低秩自适应&#xff08;LoRA&#xff09;模型设计的开源工具集主要应用于Stable Diffusion等AI绘图模型的微调&#xff0c;帮助用户高效创建定制化风格、角色或概念的轻量级模型。目前已…

探索AI的数学奇迹:Gemini 2.5 Pro如何摘得IMO金牌

🌍 引言:从人类天才到AI奇才的跨越 想象一下,一个AI模型坐在国际数学奥林匹克(IMO)的考场里,手里拿着笔(好吧,其实是处理token),面对那些让高中生们头疼不已的难题。它不是靠死记硬背,而是通过深思熟虑的推理,一步步攻克难关。这听起来像科幻小说,但2025年,这已…

MCP学习与实践

目录 1.MCP简介 1.1 MCP是什么 1.2 MCP与Agent关系&#xff1a; 1.3 MCP的架构 2. MCP原理 2.1 MCP 工作过程 2.2 MCP 通讯方式 2. MCP使用 2.1 cursor中增加MCP-SSE(高德地图MCP) 2.2 cursor中增加MCP-STDIO&#xff08;12306-MCP&#xff09; 本文详细讲解了什么是…

MySQL(187)如何使用pt-query-digest进行查询分析?

使用 pt-query-digest 工具可以帮助分析 MySQL 查询的性能&#xff0c;找出慢查询、频繁查询以及消耗资源较多的查询&#xff0c;从而为优化提供依据。以下是详细深入的使用 pt-query-digest 进行查询分析的步骤和相关示例。 一、安装 pt-query-digest pt-query-digest 是 Perc…

分享一个基于Python和Hadoop的的电信客户特征可视化分析平台 基于Spark平台的电信客服数据存储与处理系统源码

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Spark、hadoop、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题…

初识STL

一 、STL的诞生在C发展早期&#xff0c;程序员在不同的项目中需要反复编写相似的数据结构和算法。重复开发带来以下问题&#xff1a;代码冗余&#xff1a;每个项目都要重新实现基本数据结构和算法维护困难&#xff1a;不同人编写的代码风格不一致&#xff0c;难以维护效率低下&…

DDoS 防护的未来趋势:AI 如何重塑安全行业?

随着网络攻击规模和复杂性的不断升级&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击已成为企业数字化转型中的一大威胁。传统防御手段在应对智能化、动态化的攻击时逐渐显露出局限性。而人工智能&#xff08;AI&#xff09;技术的崛起&#xff0c;正为 DDoS 防护…

【每天一个知识点】深度领域对抗神经网络

Deep Domain Adversarial Neural Network&#xff08;深度领域对抗神经网络&#xff0c;DDANN&#xff09; 是一类结合 深度学习 与 领域自适应&#xff08;domain adaptation&#xff09; 思想的神经网络结构&#xff0c;主要用于不同数据域之间的知识迁移&#xff0c;尤其是在…

【C语言】深入理解预处理

文章目录一、预定义符号二、#define定义常量&#xff1a;便捷的符号替换常见用法示例&#xff1a;注意事项&#xff1a;三、#define定义宏&#xff1a;带参数的文本替换关键注意点&#xff1a;四、带有副作用的宏参数五、宏替换的规则&#xff1a;预处理的执行步骤重要注意&…

展锐平台(Android15)WLAN热点名称修改不生效问题分析

前言 在展锐Android V项目开发中&#xff0c;需要修改softAp/P2P热点名称时&#xff0c;发现集成GMS后直接修改framework层代码无效。具体表现为&#xff1a; 修改packages/modules/Wifi/WifiApConfigStore中的getDefaultApConfiguration方法编译烧录后修改不生效 问题根源在…

wsl ubuntu访问(挂载)vmware vmdk磁盘教程

之前使用VMware Workstation 虚拟机跑了个ubuntu&#xff0c;现在改用wsl了&#xff0c; 想把vmware的磁盘挂载到wsl ubuntu。一、磁盘合并我原先的vmware跑的ubuntu存在多个vmdk文件&#xff08;磁盘文件&#xff09;&#xff0c;需要先将磁盘合并成一个才方便挂载。首先你电脑…

UGUI源码剖析(3):布局的“原子”——RectTransform的核心数据模型与几何学

UGUI源码剖析&#xff08;第三章&#xff09;&#xff1a;布局的“原子”——RectTransform的核心数据模型与几何学 在前几章中&#xff0c;我们了解了UGUI的组件规范和更新调度机制。现在&#xff0c;我们将深入到这个系统的“几何学”核心&#xff0c;去剖析那个我们每天都在…