当前位置: 首页 > news >正文

SpringBoot系列之搭建WebSocket应用

SpringBoot系列之@ServerEndpoint方式开发WebSocket应用。在实时的数据推送方面,经常会使用WebSocket或者MQTT来实现,WebSocket是一种不错的方案,只需要建立连接,服务端和客户端就可以进行双向的数据通信。很多网站的客户聊天,也经常使用WebSocket技术来实现。

WebSocket简介

WebSocket是一种建立在TCP协议上的一种网络协议,与Http协议类似,端口都是80或者443,协议标识符是ws、如果是加密安全的就是wss,这个和http/https有点类似。WebSocket 连接以 HTTP 请求/响应握手开始,连接成功后,客户端可以向服务端发送消息,反之亦可,WebSocket协议支持二进制数据和文本字符串的传输。因为客户端和服务端之间只有一条TCP通信连接,以后所有的请求都使用这条连接,所以Websocket也是属于长连接。下面给出WebSocket通讯示意图:

在这里插入图片描述

WebSocket官网给出的HTTP和WebSocket的对比图:https://websocket.org/guides/road-to-websockets

在这里插入图片描述

实验环境准备

  • JDK 1.8
  • SpringBoot 3.3.0
  • Maven 3.3.9
  • 开发工具
    • IntelliJ IDEA
    • smartGit

新建WebSocket项目

在idea里新建一个module,选择Spring Initializr项目,默认选择Spring官网的https://start.spring.io
在这里插入图片描述
选择需要的依赖,这里可以选择Springboot集成的WebSocket starter
在这里插入图片描述
生成项目后,检查一下对应Maven配置文件中是否有加上spring-boot-starter-websocket

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

如果不是Springboot项目,加入spring-websocket即可

 <dependency><groupId>org.springframework</groupId><artifactId>spring-websocket</artifactId><version>5.2.1.RELEASE</version><scope>compile</scope></dependency>

创建ServerEndpoint

创建一个WebsocketServerEndpoint类,这个类是为了创建一个WebSocket服务端,这个类使用线程安全的CopyOnWriteArrayList集合来存储所有的WebSocket对象,再自定义一个socketClientCode,目的是为了客户端只和对应的服务端通信,客户端建立连接会进入onOpen方法,发送消息会调用onMessage方法

package com.example.springboot.websocket.message;import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;@ServerEndpoint("/ws/webSocketServer")
@Component
@Slf4j
public class WebSocket {private static final String PREFIX = "socketClient=";private String socketClientCode;private static CopyOnWriteArrayList<WebSocket> webSocketSet = new CopyOnWriteArrayList<>();private Session session ;@OnOpenpublic void onOpen(Session session) {this.session = session;webSocketSet.add(this);log.info("open a webSocket {}, online num: {}",getSocketClientCode(), getOnlineNum());}@OnClosepublic void onClose() {webSocketSet.remove(this);log.error("close a webSocket {}, online num:{}", getSocketClientCode(), getOnlineNum());printOnlineClientCode();}@OnErrorpublic void onError(Session session, Throwable error) {webSocketSet.remove(this);log.error("webSocket error {}, {}, online num:{}", error, getSocketClientCode(), getOnlineNum());printOnlineClientCode();}@OnMessagepublic void onMessage(String message, Session session) {log.info("receive message from client:{}", message);// 业务实现if (message.startsWith(PREFIX)) {String socketClientCode = message.substring(PREFIX.length());this.setSocketClientCode(socketClientCode);sendMessage(message);printOnlineClientCode();} else {sendMessage(message);}}/*** 发送消息** @Date 2024/06/19 16:36* @Param [message]* @return void*/public void sendMessage(String message) {if (!this.session.isOpen()) {log.warn("webSocket is close");return;}try {this.session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("sendMessage exception:{}", e);}}/*** 给客户端发送消息** @Date 2024/06/19 16:37* @Param [message, socketClientCode]* @return void*/public void sendMessageToClient(String message, String socketClientCode) {log.info("send message to client, message:{}, clientCode:{}", message, socketClientCode);printOnlineClientCode();webSocketSet.stream().forEach(ws -> {if (StrUtil.isNotBlank(socketClientCode) && StrUtil.isNotBlank(ws.getSocketClientCode()) && ws.getSocketClientCode().equals(socketClientCode)) {ws.sendMessage(message);}});}/*** 群发消息** @Date 2024/06/19 16:37* @Param [message]* @return void*/public void fanoutMessage(String message) {webSocketSet.forEach(ws -> {ws.sendMessage(message);});}private static synchronized int getOnlineNum() {return webSocketSet.size();}private void printOnlineClientCode() {webSocketSet.stream().forEach(ws -> {log.info("webSocket online:{}", ws.getSocketClientCode());});}public String getSocketClientCode() {return socketClientCode;}public void setSocketClientCode(String socketClientCode) {this.socketClientCode = socketClientCode;}
}

加上ServerEndpointExporter

Springboot项目中为了能扫描到所有的ServerEndpoint,需要注入一个ServerEndpointExporter,这个类能扫描项目里所有的ServerEndpoint类,不加的话,客户端会一直连不上服务端

package com.example.springboot.websocket.configuration;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

启动SpringBoot项目,一个WebSocket服务端就建立好了。网上找一个websocket测试网站,https://www.wetools.com/websocket,测试一下服务是否正常,如图:

在这里插入图片描述

前端WebSocket客户端

写一个WebSocket调用的客户端,启动服务器,换一下WebSocket的地址,ws://127.0.0.1:8080/ws/webSocketServer,如果是https的,就换成wss://127.0.0.1:8080/ws/webSocketServer

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>webSocket</title><style type="text/css"></style>
</head>
<body>
<h1>WebSocket Demo</h1>
<input type="button" onclick="websocket.send('666666')" value="点我发消息"/>
</body>
<script type="application/javascript">var websocket = {send: function (str) {}};window.onload = function () {if (!'WebSocket' in window) return;webSocketInit();};function webSocketInit() {websocket = new WebSocket("ws://127.0.0.1:8080/ws/webSocketServer");//建立连接websocket.onopen = function () {websocket.send("socketClient=666");console.log("成功连接到服务器");};//接收到消息websocket.onmessage = function (event) {console.log(event.data);};//连接发生错误websocket.onerror = function () {alert("WebSocket连接发生错误");};//连接关闭websocket.onclose = function () {alert("WebSocket连接关闭");};//监听窗口关闭window.onbeforeunload = function () {websocket.close()};}
</script>
</html>

走一个,在浏览器按F12,看看日志

在这里插入图片描述

后端的日志打印:

在这里插入图片描述

服务端给客户端发送消息

给客户端发送消息,为了只给对应的客户端发送消息,这里加上一个校验,只给注册的客户端发送

/*** 给客户端发送消息** @Date 2024/06/19 16:37* @Param [message, socketClientCode]* @return void*/
public void sendMessageToClient(String message, String socketClientCode) {log.info("send message to client, message:{}, clientCode:{}", message, socketClientCode);printOnlineClientCode();webSocketSet.stream().forEach(ws -> {if (StrUtil.isNotBlank(socketClientCode) && StrUtil.isNotBlank(ws.getSocketClientCode()) && ws.getSocketClientCode().equals(socketClientCode)) {ws.sendMessage(message);}});
}

写一个API接口:

package com.example.springboot.websocket.rest;import cn.hutool.json.JSONUtil;
import com.example.springboot.websocket.dto.WebSocketDto;
import com.example.springboot.websocket.message.WebSocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;@RestController
@RequestMapping("/api")
@Slf4j
public class WebSocketApiController {@Resource@Qualifier("webSocket")private WebSocket webSocket;@PostMapping@RequestMapping("/sendMessage")public ResultBean<Boolean> sendMessage(@RequestBody WebSocketDto sendDto) {log.info("webSocket发送消息给客户端:{}", JSONUtil.toJsonStr(sendDto));try {webSocket.sendMessageToClient(sendDto.getMessage(), sendDto.getSocketClient());return ResultBean.ok(true);} catch (Exception e) {log.error("发送WebSocket消息异常:{}", e);return ResultBean.badRequest("发送WebSocket消息异常", false);}}}

在这里插入图片描述

相关文章:

SpringBoot系列之搭建WebSocket应用

SpringBoot系列之ServerEndpoint方式开发WebSocket应用。在实时的数据推送方面&#xff0c;经常会使用WebSocket或者MQTT来实现&#xff0c;WebSocket是一种不错的方案&#xff0c;只需要建立连接&#xff0c;服务端和客户端就可以进行双向的数据通信。很多网站的客户聊天&…...

RK3568技术笔记十四 Ubuntu创建共享文件夹

单击“虚拟机”&#xff0c;单击“设置”&#xff0c;如图所示&#xff1a; 单击“选项”&#xff0c;选择“总是启用&#xff08;E&#xff09;”&#xff0c;单击“添加”&#xff0c;如图所示&#xff1a; 单击“下一步”&#xff0c;如图所示&#xff1a; 单击“浏览”添加…...

JavaScript 获取地理位置 Geolocation

在现代的 web 应用程序中&#xff0c;获取用户的地理位置信息是一项常见的需求。这可以用于提供个性化内容、本地化服务或者基于位置的功能。HTML5 引入了 Geolocation API&#xff0c;使得从浏览器中获取地理位置信息变得非常简单。 1. Geolocation API 简介 Geolocation AP…...

android串口助手apk下载 源码 演示 支持android 4-14及以上

android串口助手apk下载 1、自动获取串口列表 2、打开串口就开始接收 3、收发 字符或16进制 4、默认发送at\r\n 5、android串口助手apk 支持android 4-14 &#xff08;Google seral port 太老&#xff09; 源码找我 需要 用adb root 再setenforce 0进入SELinux 模式 才有权限…...

windows11 生产力工具配置

一、系统安装 官方windows11.iso镜像文件安装操作系统时&#xff0c;会强制要求联网验证&#xff0c;否则无法继续安装操作系统&#xff0c;跳过联网登录账号的方式为&#xff1a;按下【shiftF10】快捷键&#xff0c;调出cmd命令窗口&#xff0c;输入命令 OOBE\BYPASSNRO 等…...

Nacos配置中心不可用会有什么影响

服务端&#xff1a; Nacos的数据存储接口 com.alibaba.nacos.config.server.service.DataSourceService 有两种实现&#xff1a; 如果指定了mysq 作为数据库&#xff0c;则必须使用 mysql 如果是 集群方式部署Nacos&#xff0c;则必须使用mysql 如果是单例方式部署 并且 没…...

AI时代下的自动化代码审计工具

代码审计工具分享 吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 这两年一直都在提“安全左移”&…...

不懂索引,简历上都不敢写自己熟悉SQL优化

大家好&#xff0c;我是考哥。 今天给大家带来MySQL索引相关核心知识。对MySQL索引的理解甚至比你掌握SQL优化还重要&#xff0c;索引是优化SQL的前提和基础&#xff0c;我们一步步来先打好地基。 当MySQL表数据量不大时&#xff0c;缺少索引对查询性能的影响不会太大&#x…...

C# 设置PDF表单不可编辑、或提取PDF表单数据

PDF表单是PDF中的可编辑区域&#xff0c;允许用户填写指定信息。当表单填写完成后&#xff0c;有时候我们可能需要将其设置为不可编辑&#xff0c;以保护表单内容的完整性和可靠性。或者需要从PDF表单中提取数据以便后续处理或分析。 之前文章详细介绍过如何使用免费Spire.PDF…...

面试篇-求两个有序数组的交集

题目 两个有序数组&#xff0c;第一个有序数组m是1000w个元素&#xff0c;第二个有序数组n是1000个元素&#xff0c;求交集&#xff0c;需要考虑时间复杂度和空间复杂度。 解题思路 解法1&#xff1a;遍历小数组n&#xff0c;在m数组中进行折半查找&#xff0c;根据数组有序…...

Web爬虫-edu_SRC-目标列表爬取

免责声明:本文仅做技术交流与学习... 爬取后,结合暗黑搜索引擎等等进行进一步搜索. edu_src.py import requests, time from bs4 import BeautifulSoup for i in range(1, 20):url fhttps://src.sjtu.edu.cn/rank/firm/0/?page{i}print(f"正在获取第{i}页数据")s …...

云原生周刊:Harbor v2.11 版本发布 | 2024.6.17

开源项目推荐 Descheduler Descheduler 是一个工具&#xff0c;可用于优化 Kubernetes 集群中 Pod 的部署位置。它可以找到可以移动的 Pod&#xff0c;并将其驱逐&#xff0c;让默认调度器将它们重新调度到更合适的节点上。 Prowler Prowler 是一款适用于 AWS、Azure、GCP …...

低版本火狐浏览器报错:class is a reserved identifier

低版本火狐浏览器报错&#xff1a;class is a reserved identifier 原因&#xff1a;react-dnd&#xff0c;dnd-core 等node包的相关依赖有过更新&#xff0c;使得在低版本火狐浏览器中不支持 class 解决方法&#xff1a;在使用webpack打包构建时&#xff0c;编译排除node_modu…...

掌握高等数学、线性代数、概率论所需数学知识及标题建议

在数学的广袤领域中&#xff0c;高等数学、线性代数和概率论作为三大核心分支&#xff0c;不仅在理论研究中占据重要地位&#xff0c;更在实际应用中发挥着举足轻重的作用。为了深入理解和掌握这三门学科&#xff0c;我们需要掌握一系列扎实的数学知识。 高等数学所需数学知识 …...

value_and_grad

value_and_grad 是 JAX 提供的一个便捷函数&#xff0c;它同时计算函数的值和其梯度。这在优化过程中非常有用&#xff0c;因为在一次函数调用中可以同时获得损失值和相应的梯度。 以下是对 value_and_grad(loss, argnums0, has_auxFalse)(params, data, u, tol) 的详细解释&a…...

AI 已经在污染互联网了。。赛博喂屎成为现实

大家好&#xff0c;我是程序员鱼皮。这两年 AI 发展势头迅猛&#xff0c;更好的性能、更低的成本、更优的效果&#xff0c;让 AI 这一曾经高高在上的技术也走入大众的视野&#xff0c;能够被我们大多数普通人轻松使用&#xff0c;无需理解复杂的技术和原理。 其中&#xff0c;…...

Linux系统安装ODBC驱动,统信服务器E版安装psqlodbc方法

应用场景 硬件/整机信息&#xff1a;AMD平台 OS版本信息&#xff1a;服务器e版 软件信息&#xff1a;psqlodbc 12.02版本 功能介绍 部分用户在使用etl工具连接数据库时&#xff0c;需要使用到odbc驱动&#xff0c;下面介绍下服务器e版系统中编译安装此工具的相关过程。 E…...

品牌对电商平台价格的监测流程

在当今的电商时代&#xff0c;品牌商会重点关注众多电商平台&#xff0c;如淘宝、天猫、京东、拼多多、苏宁、小红书、抖音、快手等。之所以这些平台备受瞩目&#xff0c;很大程度上是因为其上的店铺数量众多&#xff0c;情况复杂。如今&#xff0c;无论是品牌的经销商还是非经…...

osgearth提示“simple.earth: file not handled”

在用vcpkg编译完osg和osgearth后&#xff0c;为了验证osgearth编译是否正确&#xff0c;进行测试&#xff0c;模型加载代码如下&#xff1a; root->addChild(osgDB::readNodeFile("simple.earth")); 此时以为是simple.earth路径的问题&#xff0c;遂改为以下代码…...

hbuilderx如何打包ios app,如何生成证书

hbuilderx可以打包ios app, 但是打包的时候&#xff0c;却需要两个证书文件&#xff0c;我们又如何生成这两个证书文件呢&#xff1f; 点击hbuilderx的官网链接&#xff0c;教程是需要使用mac电脑苹果开发者账号去创建这两个文件&#xff0c;可是问题来了&#xff0c;我们没有…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

Typeerror: cannot read properties of undefined (reading ‘XXX‘)

最近需要在离线机器上运行软件&#xff0c;所以得把软件用docker打包起来&#xff0c;大部分功能都没问题&#xff0c;出了一个奇怪的事情。同样的代码&#xff0c;在本机上用vscode可以运行起来&#xff0c;但是打包之后在docker里出现了问题。使用的是dialog组件&#xff0c;…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...