当前位置: 首页 > 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;我们没有…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统

Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...