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
创建一个Websocket的ServerEndpoint类,这个类是为了创建一个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应用。在实时的数据推送方面,经常会使用WebSocket或者MQTT来实现,WebSocket是一种不错的方案,只需要建立连接,服务端和客户端就可以进行双向的数据通信。很多网站的客户聊天&…...
RK3568技术笔记十四 Ubuntu创建共享文件夹
单击“虚拟机”,单击“设置”,如图所示: 单击“选项”,选择“总是启用(E)”,单击“添加”,如图所示: 单击“下一步”,如图所示: 单击“浏览”添加…...
JavaScript 获取地理位置 Geolocation
在现代的 web 应用程序中,获取用户的地理位置信息是一项常见的需求。这可以用于提供个性化内容、本地化服务或者基于位置的功能。HTML5 引入了 Geolocation API,使得从浏览器中获取地理位置信息变得非常简单。 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 (Google seral port 太老) 源码找我 需要 用adb root 再setenforce 0进入SELinux 模式 才有权限…...
windows11 生产力工具配置
一、系统安装 官方windows11.iso镜像文件安装操作系统时,会强制要求联网验证,否则无法继续安装操作系统,跳过联网登录账号的方式为:按下【shiftF10】快捷键,调出cmd命令窗口,输入命令 OOBE\BYPASSNRO 等…...
Nacos配置中心不可用会有什么影响
服务端: Nacos的数据存储接口 com.alibaba.nacos.config.server.service.DataSourceService 有两种实现: 如果指定了mysq 作为数据库,则必须使用 mysql 如果是 集群方式部署Nacos,则必须使用mysql 如果是单例方式部署 并且 没…...
AI时代下的自动化代码审计工具
代码审计工具分享 吉祥学安全知识星球🔗除了包含技术干货:Java代码审计、web安全、应急响应等,还包含了安全中常见的售前护网案例、售前方案、ppt等,同时也有面向学生的网络安全面试、护网面试等。 这两年一直都在提“安全左移”&…...
不懂索引,简历上都不敢写自己熟悉SQL优化
大家好,我是考哥。 今天给大家带来MySQL索引相关核心知识。对MySQL索引的理解甚至比你掌握SQL优化还重要,索引是优化SQL的前提和基础,我们一步步来先打好地基。 当MySQL表数据量不大时,缺少索引对查询性能的影响不会太大&#x…...
C# 设置PDF表单不可编辑、或提取PDF表单数据
PDF表单是PDF中的可编辑区域,允许用户填写指定信息。当表单填写完成后,有时候我们可能需要将其设置为不可编辑,以保护表单内容的完整性和可靠性。或者需要从PDF表单中提取数据以便后续处理或分析。 之前文章详细介绍过如何使用免费Spire.PDF…...
面试篇-求两个有序数组的交集
题目 两个有序数组,第一个有序数组m是1000w个元素,第二个有序数组n是1000个元素,求交集,需要考虑时间复杂度和空间复杂度。 解题思路 解法1:遍历小数组n,在m数组中进行折半查找,根据数组有序…...
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 是一个工具,可用于优化 Kubernetes 集群中 Pod 的部署位置。它可以找到可以移动的 Pod,并将其驱逐,让默认调度器将它们重新调度到更合适的节点上。 Prowler Prowler 是一款适用于 AWS、Azure、GCP …...
低版本火狐浏览器报错:class is a reserved identifier
低版本火狐浏览器报错:class is a reserved identifier 原因:react-dnd,dnd-core 等node包的相关依赖有过更新,使得在低版本火狐浏览器中不支持 class 解决方法:在使用webpack打包构建时,编译排除node_modu…...
掌握高等数学、线性代数、概率论所需数学知识及标题建议
在数学的广袤领域中,高等数学、线性代数和概率论作为三大核心分支,不仅在理论研究中占据重要地位,更在实际应用中发挥着举足轻重的作用。为了深入理解和掌握这三门学科,我们需要掌握一系列扎实的数学知识。 高等数学所需数学知识 …...
value_and_grad
value_and_grad 是 JAX 提供的一个便捷函数,它同时计算函数的值和其梯度。这在优化过程中非常有用,因为在一次函数调用中可以同时获得损失值和相应的梯度。 以下是对 value_and_grad(loss, argnums0, has_auxFalse)(params, data, u, tol) 的详细解释&a…...
AI 已经在污染互联网了。。赛博喂屎成为现实
大家好,我是程序员鱼皮。这两年 AI 发展势头迅猛,更好的性能、更低的成本、更优的效果,让 AI 这一曾经高高在上的技术也走入大众的视野,能够被我们大多数普通人轻松使用,无需理解复杂的技术和原理。 其中,…...
Linux系统安装ODBC驱动,统信服务器E版安装psqlodbc方法
应用场景 硬件/整机信息:AMD平台 OS版本信息:服务器e版 软件信息:psqlodbc 12.02版本 功能介绍 部分用户在使用etl工具连接数据库时,需要使用到odbc驱动,下面介绍下服务器e版系统中编译安装此工具的相关过程。 E…...
品牌对电商平台价格的监测流程
在当今的电商时代,品牌商会重点关注众多电商平台,如淘宝、天猫、京东、拼多多、苏宁、小红书、抖音、快手等。之所以这些平台备受瞩目,很大程度上是因为其上的店铺数量众多,情况复杂。如今,无论是品牌的经销商还是非经…...
osgearth提示“simple.earth: file not handled”
在用vcpkg编译完osg和osgearth后,为了验证osgearth编译是否正确,进行测试,模型加载代码如下: root->addChild(osgDB::readNodeFile("simple.earth")); 此时以为是simple.earth路径的问题,遂改为以下代码…...
hbuilderx如何打包ios app,如何生成证书
hbuilderx可以打包ios app, 但是打包的时候,却需要两个证书文件,我们又如何生成这两个证书文件呢? 点击hbuilderx的官网链接,教程是需要使用mac电脑苹果开发者账号去创建这两个文件,可是问题来了,我们没有…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...
