前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测)

【2023】前端加springboot实现Web Socket连接通讯(包括后端实现心跳检测)

  • 前言
  • 一、Web Socket 简绍
    • 1 为什么用 websocket?
  • 二、代码实现
    • 1、前端(html)
      • 1.1、无前端向后端发送消息
      • 1.2、有前端向后端发送消息
    • 2、后端具体代码(spring boot)
    • 2.1、maven依赖
    • 2.2、配置类
    • 3、Web Socket连接工具类
    • 2.3、Controller用于测试主动发送消息
    • 2.4、定时任务,用于调用主动向客户端发送心跳
  • 三、测试
    • 1、 测试消息发送
      • 1.1、前端日志
      • 1.2、后端日志
    • 2、测试客户端异常断开,服务器通过心跳检测自动剔除掉异常对话。

前言

写这个项目主要是有有个项目需要后端有数据实话返回前端,一开始采用前端轮询的方式,后面觉得及时性上有些不行,然后改为使用websocket ,具体实现demo以及测试流程发出来提供交流学习,

一、Web Socket 简绍

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1 为什么用 websocket?

换句话说,websocket 解决了什么问题?答案是,解决了两个主要问题:

  • 只能客户端发送请求
  • 一段时间内的频繁信息发送

假设现在需要设计一个实时预警系统的通知模块,那么作为工程师我们应该怎么设计通知的这个功能呢?因为这些系统的数据来源,一般他通过硬件设备采集到后台的,如果我们现在只有 http 协议,那么我们只能让客户端不断地轮询服务器,轮询的时间间隔越小越能接近实时的效果。可是,轮询的效率低,又浪费资源。针对这样的场景,websocket 应运而生。

在这里插入图片描述
特点:
(1)建立在 TCP 协议之上,服务器端的实现比较容易,是一个可靠的传输协议。
(2)与 HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

二、代码实现

1、前端(html)

1.1、无前端向后端发送消息

在这里插入图片描述

uid实际开发中应该使用唯一值作为当前对话的key

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
消息展示区:<br/>
<div id="textArea"></div></body>
<script>var textArea = document.getElementById('textArea');var websocket = null;//如果浏览器支持websocket就建立一个websocket,否则提示浏览器不支持websocket //uid应该要用唯一标识,为了测试方便看if('websocket' in window){websocketPage = new WebSocket('ws://localhost:8080/websocket/' + 99);}else{alert('浏览器不支持websocket!');}//建立websocket时自动调用websocketPage.onopen = function (event) {console.log('建立连接');}//关闭webscoket时自动调用websocketPage.oncolse = function (event){console.log('关闭连接');}//websocket接收到消息时调用websocketPage.onmessage = function (event){//将接收到的消息展示在消息展示区  (心跳响应回来的消息不显示)if (event.data !== "conn_success"){textArea.innerText += event.data;textArea.innerHTML += "<br/>";}}//websocket出错自动调用websocketPage.onerror = function () {alert('websocket出错');}//关闭窗口前关闭websocket连接window.onbeforeunload = function (){websocketPage.close();}</script>
</html>

1.2、有前端向后端发送消息

在这里插入图片描述

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Java后端WebSocket的Tomcat实现</title><script type="text/javascript" src="js/jquery.min.js"></script></head><body>Welcome<br/><input id="text" type="text" /><button onclick="send()">发送消息</button><hr/><button onclick="closeWebSocket()">关闭WebSocket连接</button><hr/><div id="message"></div></body><script type="text/javascript">var websocket = null;//判断当前浏览器是否支持WebSocketif('WebSocket' in window) {//改成你的地址websocket = new WebSocket("ws://localhost:8080/websocket/100");} else {alert('当前浏览器 Not support websocket')}//连接发生错误的回调方法websocket.onerror = function() {setMessageInnerHTML("WebSocket连接发生错误");};//连接成功建立的回调方法websocket.onopen = function() {setMessageInnerHTML("WebSocket连接成功");}var U01data, Uidata, Usdata//接收到消息的回调方法websocket.onmessage = function(event) {console.log(event);if (event.data !== "conn_success"){setMessageInnerHTML("接收消息:"+event.data);// setMessageInnerHTML(event);setechart()}}//连接关闭的回调方法websocket.onclose = function() {setMessageInnerHTML("WebSocket连接关闭");}// //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function() {closeWebSocket();}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {document.getElementById('message').innerHTML += innerHTML + '<br/>';}//关闭WebSocket连接function closeWebSocket() {websocket.close();}//发送消息function send() {var message = document.getElementById('text').value;websocket.send('{"msg":"' + message + '"}');setMessageInnerHTML("--------------发送消息:"+message + "");}</script>
</html>

2、后端具体代码(spring boot)

2.1、maven依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

– yml没有东西,只有一个默认端口

2.2、配置类

需要加一个 WebSocket 端点暴露 的bean 和定时器注解

@EnableScheduling  //定时器
@SpringBootApplication
public class WebSocketApplication {public static void main(String[] args) {SpringApplication.run(WebSocketApplication.class, args);}/** * 服务器端点导出 * @author zhengfuping* @date 2023/8/22 * @return ServerEndpointExporter */@Beanpublic ServerEndpointExporter getServerEndpointExporter(){return new ServerEndpointExporter();}
}

3、Web Socket连接工具类

@Slf4j
@Service
@ServerEndpoint("/websocket/{uid}")
public class WebSocketServer2 {//连接建立时长private static final long sessionTimeout = 60000;// 用来存放每个客户端对应的WebSocketServer对象private static Map<String, WebSocketServer2> webSocketMap = new ConcurrentHashMap<>();// 与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;// 接收idprivate String uid;/*** 连接建立成功调用的方法* @author zhengfuping* @date 2023/8/22* @param session* @param uid*/@OnOpenpublic void onOpen(Session session , @PathParam("uid") String uid){session.setMaxIdleTimeout(sessionTimeout);this.session = session;this.uid = uid;if (webSocketMap.containsKey(uid)){webSocketMap.remove(uid);}webSocketMap.put(uid,this);log.info("websocket连接成功编号uid: " + uid + ",当前在线数: " + getOnlineClients());try{// 响应客户端实际业务数据!sendMessage("conn_success");}catch (Exception e){log.error("websocket发送连接成功错误编号uid: " + uid + ",网络异常!!!");}}/*** 连接关闭调用的方法* @author zhengfuping* @date 2023/8/22*/@OnClosepublic void onClose(){try {if (webSocketMap.containsKey(uid)){webSocketMap.remove(uid);}log.info("websocket退出编号uid: " + uid + ",当前在线数为: " + getOnlineClients());} catch (Exception e) {log.error("websocket编号uid连接关闭错误: " + uid + ",原因: " + e.getMessage());}}/*** 收到客户端消息后调用的方法* @param message 客户端发送过来的消息* @param session*/@OnMessagepublic void onMessage(String message, Session session) {try {WebSocketServer2.sendInfo(message);log.info("websocket收到客户端编号uid消息: " + uid + ", 报文: " + message);} catch (Exception e) {log.error("websocket发送消息失败编号uid为: " + uid + ",报文: " + message);}}/*** 发生错误时调用* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("websocket编号uid错误: " + this.uid + "原因: " + error.getMessage());error.printStackTrace();}/*** 实现服务器主动推送* @author yingfeng* @date 2023/8/22 10:11* @Param * @param null* @return*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}/*** 获取客户端在线数* @author zhengfuping* @date 2023/8/22 10:11* @param*/public static synchronized int getOnlineClients() {if (Objects.isNull(webSocketMap)) {return 0;} else {return webSocketMap.size();}}/*** 单机使用,外部接口通过指定的客户id向该客户推送消息* @param key* @param message* @return boolean*/public static boolean sendMessageByWayBillId(@NotNull String key, String message) {WebSocketServer2 webSocketServer = webSocketMap.get(key);if (Objects.nonNull(webSocketServer)) {try {webSocketServer.sendMessage(message);log.info("websocket发送消息编号uid为: " + key + "发送消息: " + message);return true;} catch (Exception e) {log.error("websocket发送消息失败编号uid为: " + key + "消息: " + message);return false;}} else {log.error("websocket未连接编号uid号为: " + key + "消息: " + message);return false;}}/*** 群发自定义消息* @author zhengfuping* @date 2023/8/22 9:52* @param message*/public static void sendInfo(String message) {webSocketMap.forEach((k, v) -> {WebSocketServer2 webSocketServer = webSocketMap.get(k);try {webSocketServer.sendMessage(message);log.info("websocket群发消息编号uid为: " + k + ",消息: " + message);} catch (IOException e) {log.error("群发自定义消息失败: " + k + ",message: " + message);}});}/*** 服务端群发消息-心跳包* @author zhengfuping* @date 2023/8/22 10:09* @param message 推送数据* @return int 连接数*/public static synchronized int sendPing(String message){if (webSocketMap.size() == 0)return 0;StringBuffer uids = new StringBuffer();AtomicInteger count = new AtomicInteger();webSocketMap.forEach((uid,server)->{count.getAndIncrement();if (webSocketMap.containsKey(uid)){WebSocketServer2 webSocketServer = webSocketMap.get(uid);try {if (Integer.valueOf(uid) ==101){Integer i=1/0;}webSocketServer.sendMessage(message);if (count.equals(webSocketMap.size() - 1)){uids.append("uid");return;}uids.append(uid).append(",");} catch (Exception e) {webSocketMap.remove(uid);log.info("客户端心跳检测异常移除: " + uid + ",心跳发送失败,已移除!");}}else {log.info("客户端心跳检测异常不存在: " + uid + ",不存在!");}});log.info("客户端心跳检测结果: " + uids + "连接正在运行");return webSocketMap.size();}/*** 连接是否存在* @param uid* @return boolean*/public static boolean isConnected(String uid) {if (Objects.nonNull(webSocketMap) && webSocketMap.containsKey(uid)) {return true;} else {return false;}}
}

2.3、Controller用于测试主动发送消息

@RestController
@RequestMapping("/test")
public class WebSocketController{/*** 检验连接* @date 2023/8/22* @Param * @param webSocketId* @return * @return String*/@GetMapping("/webSocketIsConnect/{webSocketId}")public String webSocketIsConnect(@PathVariable("webSocketId") String webSocketId){if (WebSocketServer2.isConnected(webSocketId)) {return webSocketId+"正在连接";}return webSocketId+"连接断开!";}/*** 单发 消息* @author zhengfuping* @date 2023/8/22 10:25* @param webSocketId  指定 连接* @param message  数据* @param pwd 验证密码* @return String*/@GetMapping("/sendMessageByWayBillId")public String sendMessageByWayBillId(String webSocketId, String message, String pwd) {boolean flag = false;flag = WebSocketServer2.sendMessageByWayBillId(webSocketId, message);if (flag) {return "发送成功!";}return "发送失败!";}/*** 群发* @author zhengfuping* @date 2023/8/22 10:26* @param message* @param pwd*/@GetMapping("/broadSendInfo")public void sendInfo(String message, String pwd) {WebSocketServer2.sendInfo(message);}
}

2.4、定时任务,用于调用主动向客户端发送心跳

每10秒调用一次,主动检测,查看客户端连接是否异常断开,如果异常断开,则把该会话从集合中剔除掉,避免无限积压。

@Component
@Slf4j
public class WebSocketTask {@Scheduled(cron = "0/10 * * * * ?")public void clearOrders(){int num = 0;try {num = WebSocketServer2.sendPing("conn_success");} finally {log.info("websocket心跳检测结果,共【" + num + "】个连接");}}
}

三、测试

1、 测试消息发送

1.1、前端日志

在这里插入图片描述

1.2、后端日志

在这里插入图片描述

2、测试客户端异常断开,服务器通过心跳检测自动剔除掉异常对话。

因为测试不方便,只能通过断点实现效果

  1. 前端需要把主动关闭会话的注释掉,不让主动关闭
    在这里插入图片描述

  2. 先在连接关闭的地方和心跳检测地方打上断点,断点需要设置成Thread,要不然没法异步
    在这里插入图片描述

  3. 然后关闭掉一个前端页面让他把会话关闭,就会进入该断点位置,通过断点让它停住不让他去正常关闭

在这里插入图片描述4. 然后选择执行该心跳检测的断点代码
在这里插入图片描述
5. 进入心跳的循环给每个会话发送心跳检测,此时前端已经异常断开了
在这里插入图片描述
6. 因为前端已经关闭会话了,则发送心跳会失败,会直接进入catch块,然后把该会话从集合中剔除掉
在这里插入图片描述
最终日志
在这里插入图片描述

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

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

相关文章

微服务容错 Resilience4j 接口服务-容错原理

微服务容错 Resilience4j 容错原理 4.1 微服务容错简介 在⾼并发访问下&#xff0c;⽐如天猫双11&#xff0c;流量持续不断的涌⼊&#xff0c;服务之间的相互调⽤频率突然增加&#xff0c;引发系统负载过⾼&#xff0c;这时系统所依赖的服务的稳定性对系统的影响⾮常⼤&#…

vscode使用anaconda自带的python环境在终端运行时报错

目录 具体报错内容官方翻译报错讲人话解决方法 具体报错内容 CommandNotFoundError: Your shell has not been properly configured to use conda activate. If your shell is Bash or a Bourne variant, enable conda for the current user with$ echo ". E:\Anaconda/e…

【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

ES是一个分布式全文检索框架,隐藏了复杂的处理机制,核心数据分片机制、集群发现、分片负载均衡请求路由

ES是一个分布式框架&#xff0c;隐藏了复杂的处理机制&#xff0c;核心数据分片机制、集群发现、分片负载均衡请求路由。 ES的高可用架构&#xff0c;总体如下图&#xff1a; 说明&#xff1a;本文会以pdf格式持续更新&#xff0c;更多最新尼恩3高pdf笔记&#xff0c;请从下面…

字节跳动推出AI对话工具“豆包”:免费用

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 听说松松客服的小马爆料了一个消息&#xff1a;字节跳动推出了一个新的AI大模型对话工具&#xff0c;叫做“豆包”。竟然一查发现&#xff0c;早在8月18号就已经上线了呢。原来这个“豆包”其实是之…

ssm+vue“魅力”繁峙宣传网站源码和论文

ssmvue“魅力”繁峙宣传网站源码和论文102 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身…

No message found under code ‘-1‘ for locale ‘zh_CN‘.

导出中的报错&#xff1a;No message found under code -1 for locale zh_CN. 报错原因&#xff1a;页面中展示的数据和后端excel中的数据不一致导致 具体原因&#xff1a;

Sharding-JDBC分库分表四种分片算法

1. 精确分片算法 精确分片算法&#xff08;PreciseShardingAlgorithm&#xff09;精确分片算法&#xff08;与IN语句&#xff09;&#xff0c;用于处理使用单一键作为分片键的与IN进行分片的场景。需要配合StandardShardingStrategy使用 2. 范围分片算法 范围分片算法&#…

Vue基础1:生命周期汇总(vue2)

Description 生命周期图&#xff1a; 可以理解vue生命周期就是指vue实例从创建到销毁的过程&#xff0c;在vue中分为9个阶段&#xff1a;创建前/后&#xff0c;载入前/后&#xff0c;更新前/后&#xff0c;销毁前/后&#xff0c;其他&#xff1b;常用的有&#xff1a;created&…

随记-多租户数据隔离

数据隔离 DataBase 隔离 ( 独立数据库 )Schema 隔离 ( 共享数据库&#xff0c;但隔离数据架构 )Table 隔离 ( 共享数据库&#xff0c;共享数据架构 ) DB 隔离 即一个租户一个数据库&#xff0c;这种方案的用户数据隔离级别最高&#xff0c;安全性最好&#xff0c;但成本较高 …

arm版Linux下安装es集群

背景&#xff1a;由于生产上网络没通&#xff0c;没办法&#xff0c;只能自己安装一个es集群的测试环境了&#xff0c;我的电脑是Mac M2&#xff0c;安装的Linux是centos7&#xff0c;也是arm版的。 第一步&#xff1a;查看自己Linux系统的版本 命令&#xff1a;uname -a 例如…

C语言圣经KR笔记 1.10外部变量和作用域

1.10外部变量和作用域 上一节main中的变量&#xff0c;如line、longest等等&#xff0c;对main来说是私有的或者说是局部的。因为它们是在main中定义的&#xff0c;其他函数不能直接访问它们。其他函数中的变量也是如此&#xff0c;例如&#xff0c;getline中的变量 i 与copy中…

睿趣科技:抖音开网店卖玩具怎么样

近年来&#xff0c;随着社交媒体平台的飞速发展&#xff0c;抖音作为一款短视频分享应用也迅速崭露头角。而在这个充满创业机遇的时代背景下&#xff0c;许多人开始探索在抖音平台上开设网店&#xff0c;尤其是卖玩具类商品&#xff0c;那么抖音开网店卖玩具究竟怎么样呢? 首先…

傅里叶变换(FFT)笔记存档

参考博客&#xff1a;https://www.luogu.com.cn/blog/command-block/fft-xue-xi-bi-ji 目录&#xff1a; FFT引入复数相关知识单位根及其相关性质DFT过程&#xff08;难点&#xff09;DFT结论&#xff08;重要&#xff09;IDFT结论&#xff08;重要&#xff09;IDFT结论证明&…

巨人互动|游戏出海游戏出海需要考虑哪些方面?

游戏出海是指将游戏产品推向国外市场&#xff0c;以扩大用户群体和增加盈利空间&#xff0c;那么要成功地进行游戏出海&#xff0c;需要考虑哪些方面呢&#xff1f;本文小编对此来讲讲吧&#xff01; 1、目标市场选择 选择适合游戏产品的目标市场是出海的首要考虑因素&#xf…

word中标题及公式自动编号

word中公式自动编号 1. 实现目标2. 详细步骤2.1 添加自动编号功能2.2 输入标题并编号2.3 新建公式2.3.1 编辑公式2.3.4 公式编号的交叉引用2.3.5 公式位置变动以及更新正文中的编号 在word中自动编号公式一直是一个老大难问题&#xff0c;现在通过总结网友们提供的方法&#xf…

2023年京东方便食品行业数据分析(京东数据报告)

​疫情中方便食品的销售一度火爆&#xff0c;但随着当前消费场景的开放&#xff0c;方便食品销售又恢复常态并开始下滑。根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年7月份&#xff0c;京东平台方便食品的销量为800万&#xff0c;环比降低约23%&#xff0c;同比降…

C++贪吃蛇(控制台版)

C自学精简实践教程 目录(必读) 目录 主要考察 需求 输入文件 运行效果 实现思路 枚举类型 enum class 启动代码 输入文件data.txt 的内容 参考答案 学生实现的效果 主要考察 模块划分 文本文件读取 UI与业务分离 控制台交互 数据抽象 需求 用户输入字母表示方…

外观模式:简化复杂子系统的访问与使用

文章目录 1. 简介2. 外观模式的基本结构3. 外观模式的实现步骤4. 外观模式的应用与实例4.1 图形界面库的外观模式应用4.2 文件压缩与解压缩的外观模式应用4.3 订单处理系统的外观模式应用 5. 外观模式的优缺点5.1 优点5.2 缺点 6. 总结 1. 简介 外观模式是一种结构型设计模式&…

基于java+springboot+vue的点餐平台网站-lw-源码

​ 系统介绍&#xff1a; 随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多商家的之中&#xff0c;随之就产生了“点餐平台网站”&#xff0c;这样就让点餐平台网站更加方便简单。 对于本点餐平台网站的设计来说…