Spring WebSocket实现实时通信的详细教程

简介

WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。

我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。

Spring WebSocket是Spring框架的一部分,提供了在Web应用程序中实现实时双向通信的能力。本教程将引导你通过一个简单的例子,演示如何使用Spring WebSocket建立一个实时通信应用。

准备工作

确保你的项目中已经引入了Spring框架的WebSocket模块。你可以通过Maven添加以下依赖:

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

创建WebSocket配置类(实现WebSocketConfigurer接口)

首先,创建一个配置类,用于配置WebSocket的相关设置。

package com.ci.erp.human.config;import com.ci.erp.human.handler.WebSocketHandler;
import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;/**** Websocket配置类** @author lucky_fd* @since 2024-01-17*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册websocket处理器和拦截器registry.addHandler(webSocketHandler(), "/websocket/server").addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*").addInterceptors(webSocketHandleInterceptor()).withSockJS();}@Beanpublic WebSocketHandler webSocketHandler() {return new WebSocketHandler();}@Beanpublic WebSocketHandleInterceptor webSocketHandleInterceptor() {return new WebSocketHandleInterceptor();}
}

上面的配置类使用@EnableWebSocket注解启用WebSocket,并通过registerWebSocketHandlers方法注册WebSocket处理器。

  • registerWebSocketHandlers:这个方法是向spring容器注册一个handler处理器及对应映射地址,可以理解成MVC的Handler(控制器方法),websocket客户端通过请求的url查找处理器进行处理

  • addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来做一些事情。

  • setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

  • withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

创建WebSocket消息处理器(实现TextWebSocketHandler 接口)

接下来,创建一个消息处理器,处理客户端发送的消息。

package com.ci.erp.human.handler;import cn.hutool.core.util.ObjectUtil;
import com.ci.erp.common.core.utils.JsonUtils;
import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/**** websocket处理类* 实现WebSocketHandler接口** - websocket建立连接后执行afterConnectionEstablished回调接口* - websocket关闭连接后执行afterConnectionClosed回调接口* - websocket接收客户端消息执行handleTextMessage接口* - websocket传输异常时执行handleTransportError接口** @author lucky_fd* @since 2024-01-17*/public class WebSocketHandler extends TextWebSocketHandler {/*** 存储websocket客户端连接* */private static final Map<String, WebSocketSession> connections = new HashMap<>();/*** 建立连接后触发* */@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("成功建立websocket连接");// 建立连接后将连接以键值对方式存储,便于后期向客户端发送消息// 以客户端连接的唯一标识为key,可以通过客户端发送唯一标识connections.put(session.getRemoteAddress().getHostName(), session);System.out.println("当前客户端连接数:" + connections.size());}/*** 接收消息* */@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("收到消息: " + message.getPayload());// 收到客户端请求消息后进行相应业务处理,返回结果this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));}/*** 传输异常处理* */@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {super.handleTransportError(session, exception);}/*** 关闭连接时触发* */@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("触发关闭websocket连接");// 移除连接connections.remove(session.getRemoteAddress().getHostName());}@Overridepublic boolean supportsPartialMessages() {return super.supportsPartialMessages();}/*** 向连接的客户端发送消息** @author lucky_fd* @param clientId 客户端标识* @param message 消息体**/public void sendMessage(String clientId, TextMessage message) {for (String client : connections.keySet()) {if (client.equals(clientId)) {try {WebSocketSession session = connections.get(client);// 判断连接是否正常if (session.isOpen()) {session.sendMessage(message);}} catch (IOException e) {System.out.println(e.getMessage());}break;}}}
}

通过消息处理器,在开发中我们就可以实现向指定客户端或所有客户端发送消息,实现相应业务功能。

创建拦截器

拦截器会在握手时触发,可以用来进行权限验证

package com.ci.erp.human.interceptor;import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;import java.util.Map;/**** Websocket拦截器类** @author lucky_fd* @since 2024-01-17*/public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {System.out.println("拦截器前置触发");return super.beforeHandshake(request, response, wsHandler, attributes);}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {System.out.println("拦截器后置触发");super.afterHandshake(request, response, wsHandler, ex);}
}

创建前端页面客户端

最后,创建一个简单的HTML页面,用于接收用户输入并显示实时聊天信息。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Spring WebSocket Chat</title><script src="https://code.jquery.com/jquery-3.6.4.min.js"></script><script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
</head>
<body>请输入:<input type="text" id="message" placeholder="Type your message">
<button onclick="sendMessage()">Send</button>
<button onclick="websocketClose()">关闭连接</button>
<div id="chat"></div><script>var socket = null;if ('WebSocket' in window) {// 后端服务port为22900socket = new WebSocket("ws://localhost:22900/websocket/server");} else if ('MozWebSocket' in window) {socket = new MozWebSocket("ws://localhost:22900/websocket/server");} else {socket = new SockJS("http://localhost:22900/sockjs/server");}// 接收消息触发socket.onmessage = function (event) {showMessage(event.data);};// 创建连接触发socket.onopen = function (event) {console.log(event.type);};// 连接异常触发socket.onerror = function (event) {console.log(event)};// 关闭连接触发socket.onclose = function (closeEvent) {console.log(closeEvent.reason);};//发送消息function sendMessage() {if (socket.readyState === socket.OPEN) {var message = document.getElementById('message').value;socket.send(message);console.log("发送成功!");} else {console.log("连接失败!");}}function showMessage(message) {document.getElementById('chat').innerHTML += '<p>' + message + '</p>';}function websocketClose() {socket.close();console.log("连接关闭");}window.close = function () {socket.onclose();};</script></body>
</html>

这个页面使用了WebSocket对象来建立连接,并通过onmessage监听收到的消息。通过输入框发送消息,将会在页面上显示。

测试结果:

后端日志:

在这里插入图片描述

前端界面:

在这里插入图片描述

Java客户端

添加依赖

<dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.4.0</version>
</dependency>

创建客户端类(继承WebsocketClient)

package com.river.websocket;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;import java.net.URI;
import java.net.URISyntaxException;public class MyWebSocketClient extends WebSocketClient {MyWebSocketClient(String url) throws URISyntaxException {super(new URI(url));}// 建立连接@Overridepublic void onOpen(ServerHandshake shake) {System.out.println(shake.getHttpStatusMessage());}// 接收消息@Overridepublic void onMessage(String paramString) {System.out.println(paramString);}// 关闭连接@Overridepublic void onClose(int paramInt, String paramString, boolean paramBoolean) {System.out.println("关闭");}// 连接异常@Overridepublic void onError(Exception e) {System.out.println("发生错误");}
}

测试websocket

package com.river.websocket;import org.java_websocket.enums.ReadyState;import java.net.URISyntaxException;/*** @author lucky_fd* @date 2024-1-17*/
public class Client {public static void main(String[] args) throws URISyntaxException, InterruptedException {MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");client.connect();while (client.getReadyState() != ReadyState.OPEN) {System.out.println("连接状态:" + client.getReadyState());Thread.sleep(100);}client.send("测试数据!");client.close();}
}

参考链接:

  • 通过注解方式实现websocket:springboot集成websocket持久连接(权限过滤+拦截)
  • spring websocket实现前后端通信

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

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

相关文章

Jupyter Notebook

2017年左右在大学里都听说过Jupyter Notebook&#xff0c;并且也安装用了一段时间&#xff0c;后来不知道什么原因没有用了。估计是那时候写代码的时候多一些&#xff0c;因为它可以直接写代码并运行结果&#xff0c;现在不怎么写代码了。 介绍 后缀名为.ipynb的json格式文件…

WAF攻防相关知识点总结2-代码免杀绕过

WAF的检测除了有对于非正常的流量检测外还对于非正常的数据包特征进行检测 以宝塔为例 在宝塔的后台可以放置一句话木马的文件 宝塔不会对于这个文件进行拦截&#xff0c;但是一旦我们使用菜刀蚁剑等webshell工具去进行连接的时候&#xff0c;数据报中有流量特征就会被拦截 …

JS封装本地缓存的设置,读取,移除,清空方法及使用示例

我封装了一个JS通用的缓存管理对象&#xff0c;可以提供缓存的设置&#xff0c;读取&#xff0c;移除&#xff0c;清空操作&#xff0c;使用也很方便&#xff0c;封装方法的代码在最下方。 Q: 为什么不直接用原生的缓存方法&#xff0c;要封装&#xff1f; A1:原生的缓存管理…

【51单片机】数码管的静态与动态显示(含消影)

数码管在现实生活里是非常常见的设备&#xff0c;例如 这些数字的显示都是数码管的应用。 目录 静态数码管&#xff1a;器件介绍&#xff1a;数码管的使用&#xff1a;译码器的使用&#xff1a;缓冲器&#xff1a; 实现原理&#xff1a;完整代码&#xff1a; 动态数码管&#…

Docker 安装 MySQ

Docker 安装 MySQL MySQL 是世界上最受欢迎的开源数据库。凭借其可靠性、易用性和性能&#xff0c;MySQL 已成为 Web 应用程序的数据库优先选择。 1、查看可用的 MySQL 版本 访问 MySQL 镜像库地址&#xff1a;https://hub.docker.com/_/mysql?tabtags 。 可以通过 Sort b…

写点东西《什么是网络抓取?》

写点东西《什么是网络抓取&#xff1f;》 什么是网络抓取&#xff1f; 网络抓取合法吗&#xff1f; 什么是网络爬虫&#xff0c;它是如何工作的&#xff1f; 网络爬虫示例 网络抓取工具 结论 您是否曾经想同时比较多个网站上同一件商品的价格&#xff1f;或者自动提取您最喜欢的…

生成式对抗网络GAN

Generative Adversarial Nets由伊恩古德费洛&#xff08;Ian J.Goodfellow&#xff09;等人于2014年发表在Conference on Neural Information Processing Systems (NeurIPS)上。NeurIPS是机器学习和计算神经科学领域的顶级国际学术会议之一。 1. GAN在哪些领域大放异彩 图像生…

Rust之旅 - Rust概念、Windows安装、环境配置

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 系列专栏目录 [Java项目…

手动添加测试用例配置输入参数和期望值

1.选中函数&#xff0c;点击右键选择插入测试用例。这里所选择的插入测试用例区别于之前的测试用例的地方在于&#xff0c;这里插入测试用例是手动配置的&#xff0c;之前的是自动生成的。手动配置可以自定义选择输入参数和期望值。 2.添加测试用例后&#xff0c;点击测试用例&…

克魔助手工具详解、数据包抓取分析、使用教程

目录 摘要 引言 克魔助手界面 克魔助手查看数据捕获列表 数据包解析窗口 数据包数据窗口 克魔助手过滤器表达式的规则 抓包过滤器实例 总结 参考资料 摘要 本文介绍了克魔助手工具的界面和功能&#xff0c;包括数据包的捕获和分析&#xff0c;以及抓包过滤器的使用方…

电梯节能落座-智慧停车场️,电梯不仅可载人也可以载汽车!

电梯不仅可载人也可以载汽车哦&#xff01; 在北京市丰台区&#xff0c;有这么一个智慧停车场&#x1f17f;️ &#xff0c;共298个停车位&#xff0c;全部智能一体化&#xff0c;简直是“豪华” “智能” 的象征。 523能源&#xff1a;小伍&#xff0c;你跑题了... 小伍&am…

MySQL核心SQL

一.结构化查询语言 SQL是结构化查询语言&#xff08;Structure Query Language&#xff09;&#xff0c;它是关系型数据库的通用语言。 SQL 主要可以划分为以下 3 个类别&#xff1a; DDL&#xff08;Data Definition Languages&#xff09;语句 数据定义语言&#xff0c;这…

Discuz论坛网站登录账号操作慢,必须强制刷新才会显示登录怎么办?

飞飞发现在登录服务器大本营账号时&#xff0c;输入账号密码登录后还是显示的登录框&#xff0c;强制刷新后才知道已经登录了&#xff0c;每次都要刷新才能正常显示&#xff0c;非常影响用户体验&#xff0c;于是在网上找了类似的问题故障解决方法&#xff0c;目前问题已经解决…

AWS边缘媒体安全交付方案

企业如何在AWS上的边缘站点&#xff0c;安全的将优质视频内容交付给用户&#xff0c;并且禁止哪些未经过授权的访问&#xff1f;九河云将基于AWS平台提供边缘媒体安全交付解决方案 解决方案详情 在通过 Amazon CloudFront 交付时&#xff0c;免受未经授权的访问。基于添加到交…

多标签节点分类

Multi-Label Node Classification on Graph-Structured Data,TMLR’23 Code 学习笔记 图结构数据的多标签分类 节点表示或嵌入方法 通常会生成查找表&#xff0c;以便将相似的节点嵌入的更近。学习到的表示用作各种下游预测模块的输入特征。 表现突出的方法是基于随机游走(ran…

docker部署项目,/var/lib/docker/overlay2目录满了如何清理?

docker部署项目&#xff0c;/var/lib/docker/overlay2目录满了如何清理&#xff1f; 一、问题二、解决1、查看 /var/lib/docker 目录&#xff08;1&#xff09;、containers 目录&#xff08;2&#xff09;、volumes 目录&#xff08;3&#xff09;、overlay2 目录 2、清理&…

台灯护眼有用吗?分享备考专用的护眼台灯

说到台灯相信大家都不陌生&#xff0c;如今基本是每个家庭都会备上一台。很多家长会买上一台给孩子学习使用&#xff0c;还有些学生党、办公族夜晚学习工作时也会用得上它。但普通的台灯会出现光照范围不够大、光线过度集中、光线均匀度不足、产生眩光等问题&#xff0c;可能会…

基于 IDEA 进行 Maven 依赖管理

一、依赖管理概念 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

Redis--Zset使用场景举例(滑动窗口实现限流)

文章目录 前言什么是滑动窗口zset实现滑动窗口小结附录 前言 在Redis–Zset的语法和使用场景举例&#xff08;朋友圈点赞&#xff0c;排行榜&#xff09;一文中&#xff0c;提及了redis数据结构zset的指令语法和一些使用场景&#xff0c;今天我们使用zset来实现滑动窗口限流&a…