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

Springboot 整合 WebSocket 实现聊天室功能

目录

  • 前言
  • 一、WebSocket原理
  • 二、Spring Boot集成WebSocket
    • 2.1. 引入依赖
    • 2.2 配置类WebSocketConfig
    • 2.3 WebSocketServer 类
    • 2.4 前端代码 index.html
    • 2.5 Controller访问首页


前言

WebSocket概述:
在日常的web应用开发中,常见的是前端向后端发起请求,有些时候会涉及到前后端互发消息,这时候就用到了WebSocket。


一、WebSocket原理

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它通过一个简单的握手过程来建立连接,然后在连接上进行双向数据传输。与传统的HTTP请求不同,WebSocket连接一旦建立,就可以在客户端和服务器之间保持打开状态,直到被任何一方关闭。

核心特点包括:

  1. 全双工通信:客户端和服务器可以同时发送和接收消息。
  2. 持久连接:一旦建立连接,就可以持续进行数据交换,无需像HTTP那样频繁地建立新的连接。
  3. 低延迟:由于连接是持久的,数据可以几乎实时地发送和接收。
  4. 轻量级协议:WebSocket协议的头部信息非常简单,减少了数据传输的开销。

二、Spring Boot集成WebSocket

代码结构:
在这里插入图片描述

2.1. 引入依赖

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

2.2 配置类WebSocketConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;/*** WebSocket配置类。* 用于启用Spring WebSocket支持,通过@Bean注解注册ServerEndpointExporter,* 从而允许使用@ServerEndpoint注解定义WebSocket端点。*/
@Configuration
public class WebSocketConfig {/*** 注册ServerEndpointExporter Bean。* ServerEndpointExporter是Spring提供的一个工具类,* 它会扫描并注册所有使用@ServerEndpoint注解的类为WebSocket端点。** @return ServerEndpointExporter实例*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}/*** 通信文本消息和二进制缓存区大小* 避免报文过大时,Websocket 1009 错误*/@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();// 文本/二进制消息最大缓冲区(10MB)container.setMaxTextMessageBufferSize(1024 * 1024 * 10);container.setMaxBinaryMessageBufferSize(1024 * 1024 * 10);// 最大会话空闲超时时间(1小时)container.setMaxSessionIdleTimeout(60 * 60 * 1000L);return container;}
}

2.3 WebSocketServer 类

WebSocketServer 类实现了 WebSocket 服务端的功能。 负责处理 WebSocket 连接的建立、关闭、消息接收和发送等操作。

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;/*** WebSocketServer 类实现了 WebSocket 服务端的功能。* 它负责处理 WebSocket 连接的建立、关闭、消息接收和发送等操作。*/
@Component
@Slf4j
@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {// 静态变量,用于记录当前在线连接数private static int onlineCount = 0;// 存储所有连接的 WebSocketServer 实例@Getterprivate static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();// 当前连接的会话对象private Session session;// 客户端唯一标识符private String sid = "";/*** 连接建立成功时调用的方法。** @param session 当前连接的会话对象* @param sid 客户端唯一标识符*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {this.session = session;webSocketSet.add(this);     // 将当前实例加入集合this.sid = sid;addOnlineCount();           // 在线数加1try {sendMessage("WebSocket 连接成功");  // 发送连接成功的消息log.info("有新窗口开始监听:{},当前在线人数为:{}", sid, getOnlineCount());} catch (IOException e) {log.error("websocket IO Exception");}}/*** 连接关闭时调用的方法。*/@OnClosepublic void onClose() {webSocketSet.remove(this);  // 从集合中移除当前实例subOnlineCount();           // 在线数减1log.info("释放的sid为:{}", sid);log.info("有一个连接关闭!当前在线人数为{}", getOnlineCount());}/*** 接收到客户端消息时调用的方法。** @param message 客户端发送的消息* @param session 当前连接的会话对象*/@OnMessagepublic void onMessage(String message, Session session) {log.info("收到来自窗口{}的信息:{}", sid, message);// 群发消息for (WebSocketServer item : webSocketSet) {if (Objects.equals(item.sid, this.sid)) {continue;}sendMessageToClient(item, message);}}/*** 实现服务器主动推送消息的方法,并统一处理异常。** @param client 要推送的客户端实例* @param message 要推送的消息*/private void sendMessageToClient(WebSocketServer client, String message) {try {client.sendMessage(message);} catch (IOException e) {log.error("向客户端 {} 发送消息时出错: {}", client.sid, message, e);}}/*** 群发自定义消息给指定的客户端。** @param message 要发送的消息* @param sid 客户端唯一标识符,为 null 时发送给所有客户端* @throws IOException 如果发送消息时发生 I/O 错误*/public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {log.info("推送消息到窗口" + sid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet) {try {if (sid == null) {// 如果 sid 为 null,则发送给所有客户端item.sendMessage(message);} else if (item.sid.equals(sid)) {// 如果 sid 匹配,则只发送给该客户端item.sendMessage(message);}} catch (IOException e) {log.error("向客户端 {} 发送消息时出错: {}", item.sid, message, e);}}}/*** 发生错误时调用的方法。** @param session 当前连接的会话对象* @param error 发生的错误*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误");error.printStackTrace();}/*** 实现服务器主动推送消息的方法。** @param message 要推送的消息* @throws IOException 如果发送消息时发生 I/O 错误*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}/*** 获取当前在线连接数。** @return 当前在线连接数*/public static synchronized int getOnlineCount() {return onlineCount;}/*** 增加在线连接数。*/public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}/*** 减少在线连接数。*/public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}
}

2.4 前端代码 index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>WebSocket 聊天室</title><script src="https://autherp.jd.com/js/jquery.js"></script><style>.time {font-size: 0.8em;display: block;margin-bottom: 5px;}.user-msg {background-color: #90EE90;padding: 8px;border-radius: 8px;max-width: 70%;display: inline-block;}.system-msg {background-color: #D3D3D3;padding: 8px;border-radius: 8px;max-width: 70%;display: inline-block;font-size: 0.9em;}#message-box {height: 300px;overflow-y: auto;margin-bottom: 10px;border: 1px solid #ccc;padding: 10px;border-radius: 4px;}.message-right {text-align: right;margin: 10px 0;}.message-left {text-align: left;margin: 10px 0;}.message-center {text-align: center;margin: 10px 0;}.container {max-width: 600px;margin: 20px auto;padding: 20px;border: 1px solid #ddd;border-radius: 8px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}.input-group {display: flex;gap: 10px;}.input-group input[type="text"] {flex-grow: 1;padding: 8px;border: 1px solid #ccc;border-radius: 4px;}.btn-primary, .btn-danger {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;}.btn-primary {background-color: #007BFF;color: white;}.btn-primary:hover {background-color: #0056b3;}.btn-danger {background-color: #DC3545;color: white;}.btn-danger:hover {background-color: #c82333;}/* 添加新样式让标题和按钮居中 */.container h2,.container .btn-danger {text-align: center;display: block;margin-left: auto;margin-right: auto;}/* 为按钮添加一些外边距,使其看起来更美观 */.container .btn-danger {margin-top: 10px;}</style>
</head>
<body>
<div class="container"><h2>WebSocket 聊天室</h2><!-- 添加显示 sid 的元素 --><p id="sid-display">当前用户 SID: <span id="sid-value"></span></p><!-- 消息显示区域 --><div id="message-box"></div><!-- 输入框与发送按钮 --><div class="input-group"><input type="text" id="text" placeholder="请输入消息..." /><button class="btn-primary" onclick="send()">发送</button></div><hr/><!-- 关闭连接按钮 --><button class="btn-danger" onclick="closeWebSocket()">关闭 WebSocket 连接</button>
</div><script type="text/javascript">let websocket = '';// 获取当前页面 URL 中的 sid 参数或随机生成一个function getSid() {const urlParams = new URLSearchParams(window.location.search);return urlParams.get('sid') || Math.floor(1000 + Math.random() * 9000); // 4位数字}const sid = getSid();const wsUrl = `ws://127.0.0.1:9999/api/websocket/${sid}`;// 页面加载完成后更新 sid 显示window.onload = function() {document.getElementById('sid-value').textContent = sid;};// 初始化 WebSocketif ('WebSocket' in window) {websocket = new WebSocket(wsUrl);} else {alert('当前浏览器不支持 WebSocket');}// 连接成功websocket.onopen = function () {console.log('WebSocket 连接成功');};// 接收消息websocket.onmessage = function (event) {addMessage(event.data);};// 错误处理websocket.onerror = function () {console.log('WebSocket 连接发生错误');};// 关闭连接websocket.onclose = function () {this.closeWebSocket();console.log('WebSocket 连接已关闭');};// 页面关闭前断开连接window.onbeforeunload = function () {this.closeWebSocket();};// 发送消息function send() {let message = document.getElementById('text').value.trim();if (!message) return;if (websocket && websocket.readyState === WebSocket.OPEN) {websocket.send(`{"msg":"${message}","sid":"${sid}", "time": "${new Date().toLocaleTimeString()}"}`);addMessage(`{"msg":"${message}","sid":"${sid}", "time": "${new Date().toLocaleTimeString()}"}`);document.getElementById('text').value = '';} else {addMessage("WebSocket 连接未建立,请稍后再试。");}}//关闭WebSocket连接function closeWebSocket() {if (websocket) {websocket.close();}}// 添加消息到聊天区function addMessage(content) {let msgBox = document.getElementById('message-box');const time = new Date().toLocaleTimeString();const div = document.createElement('div');let messageData;let isSystemMessage = false;try {messageData = JSON.parse(content);} catch (e) {isSystemMessage = true;}if (isSystemMessage) {div.className = 'message-center';div.innerHTML = `<span class="time">${time}</span><span class="system-msg"> ${content}</span>`;} else {const isMyMessage = String(messageData.sid) === String(sid);div.className = isMyMessage ? 'message-right' : 'message-left';div.innerHTML = `<span class="time">${messageData.time}</span><span class="${isMyMessage ? 'user-msg' : 'system-msg'}">${isMyMessage ? '' : '用户#' + messageData.sid + ':'} ${messageData.msg}</span>`;}msgBox.appendChild(div);msgBox.scrollTop = msgBox.scrollHeight;}</script>
</body>
</html>

2.5 Controller访问首页

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class TestController {@RequestMapping("/")public String index(){return "index.html";}
}

打开多个网页窗口,访问ip:端口
在这里插入图片描述
在这里插入图片描述


相关文章:

Springboot 整合 WebSocket 实现聊天室功能

目录 前言一、WebSocket原理二、Spring Boot集成WebSocket2.1. 引入依赖2.2 配置类WebSocketConfig2.3 WebSocketServer 类2.4 前端代码 index.html2.5 Controller访问首页 前言 WebSocket概述&#xff1a; 在日常的web应用开发中&#xff0c;常见的是前端向后端发起请求&…...

用 Trae IDE 打造一个桌面小爬虫:从 PyQt5 开始,轻松采集掘金首页内容

很多程序员都有这样的经历&#xff1a;刷掘金、看文章、找灵感、追热点。但你有没有想过&#xff0c;有一天让“爬虫”代替你去浏览这些内容&#xff1f;自动提取标题、作者、点赞数、评论数&#xff0c;一键生成你的专属“技术热点日报”。 今天我们就用 Trae IDE PyQt5 来完…...

python和风api获取天气(JSON Web Token)

下载安装openssl 默认安装目录&#xff0c;添加C:\Program Files\OpenSSL-Win64\bin到用户Path环境变量 打开cmd&#xff0c;执行命令&#xff0c;会生成两个文件ed25519-private.pem&#xff0c;ed25519-public.pem openssl genpkey -algorithm ED25519 -out ed25519-privat…...

模板应用更新同步到所有开发中的应用

需求是为多个 Vue 3 应用方便地同步模板更新&#xff0c;并且模板自身也可能演进&#xff0c;采用 Git 上游仓库 (Upstream) 策略。这种方法在操作上相对直观&#xff0c;更贴近常规的 Git 工作流&#xff0c;并且能较好地处理模板更新中可能涉及到的配置文件、依赖项以及 Vue …...

git和gitee的常用语句命令

Git 和 Gitee 常用命令及语法规则 一、Git 基础配置与初始化 在使用 Git 进行版本控制之前&#xff0c;需要进行用户签名的配置。此操作只需执行一次即可生效。 git config --global user.name "用户名" # 设置用户名 git config --global user.email "邮箱…...

52、C# 泛型 (Generics)

泛型是 C# 2.0 引入的一项强大功能&#xff0c;它允许你编写可以处理多种数据类型的代码&#xff0c;而无需为每种类型重复编写相同的逻辑。泛型提高了代码的重用性、类型安全性和性能。 基本概念 泛型类 public class GenericClass<T> {private T _value;public Gene…...

理解 Vue 2 的响应式原理:数据劫持与依赖收集的背后

在Vue2中,响应式系统是一切魔法的源头,无论是模板中的数据绑定,还是computed,watch的精准监听,都离不开Vue背后的响应式机制,本文将从源码角度出发,结合实例,深入剖析vue2是如何通过数据劫持(Object.defineProperty)和依赖收集实现响应式的 一.Vue2响应式系统基本原理 vue2中…...

深入理解 Pinia:Vue 状态管理的革新与实践

深入理解 Pinia&#xff1a;Vue 状态管理的革新与实践 一、引言 在 Vue.js 应用开发中&#xff0c;状态管理是构建复杂应用的关键环节。Pinia 作为新一代 Vue 状态管理库&#xff0c;凭借其简洁的 API 设计、强大的开发体验和良好的性能表现&#xff0c;逐渐成为 Vue 开发者的…...

Dubbo高频面试题

引言 作为分布式服务框架的标杆&#xff0c;Dubbo凭借其高性能RPC通信、灵活的服务治理能力和丰富的容错机制&#xff0c;成为Java技术栈中微服务领域的核心考点。本文系统梳理Dubbo高频面试核心知识点&#xff0c;涵盖容错策略、负载均衡、注册中心原理、服务上下线感知等关键…...

Allegro X PCB设计小诀窍--05.如何在Allegro X中实现隐藏电源飞线效果

背景介绍&#xff1a;在PCB设计过程中&#xff0c;布线初期印制板上的飞线错综复杂&#xff0c;信号线和电源线混合交错&#xff0c;但是实际上对于多层板来说&#xff0c;电源的网络一般是通过电源层铺铜连接的&#xff0c;很少需要走线&#xff0c;这样混乱的情况会严重影响设…...

一篇文章教会你ESP8266串口WIFI无线模块实现物联网无线收发,附STM32代码示例

目录 一、ESP-01S无线模块: &#xff08;1&#xff09;特点&#xff1a; &#xff08;2&#xff09;管脚定义&#xff1a; &#xff08;3&#xff09;启动模式&#xff1a; 二、ESP-01S出厂固件烧录&#xff1a; &#xff08;1&#xff09;引脚接线&#xff1a; &#xff0…...

算法-基础算法

一、枚举算法 也称为穷举算法&#xff0c;指的是按照问题本身的性质&#xff0c;一一列举出该问题所有可能的解&#xff0c;并在逐一列举的过程中&#xff0c;将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中&#xff0c;既不能遗漏也不能重复 1. 问题 …...

特种设备作业人员-G3锅炉水处理如何备考学习?

备考特种设备作业人员 - G3 锅炉水处理可以从了解考试信息、掌握基础知识、选择学习资料、制定学习计划等多个方面入手&#xff0c;以下是具体的建议&#xff1a; ​ ​1.了解考试信息 *明确考试大纲&#xff1a;详细了解 G3 锅炉水处理考试大纲的要求&#xff0c;明确考试的…...

Reactor模式详解:高并发场景下的事件驱动架构

文章目录 前言一、Reactor模式核心思想二、工作流程详解2.1 服务初始化阶段2.2 主事件循环2.3 子Reactor注册流程2.4 IO事件处理时序2.5 关键设计要点 三、关键实现技术四、实际应用案例总结 前言 在现代高性能服务器开发中&#xff0c;如何高效处理成千上万的并发连接是一个关…...

UniApp 生产批次管理模块技术文档

UniApp 生产批次管理模块技术文档 1. 运行卡入站页面 (RunCardIn) 1.1 页面结构 <template><!-- 页面容器 --><view class"runCardIn" :style"{ paddingTop: padding }"><!-- 页头组件 --><pageHeader :title"$t(MENU:…...

项目日记 -Qt音乐播放器 -设置任务栏图标与托盘图标

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【Qt音乐播放器】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 代码仓库&#xff1a;MusicPlayer v1.0版视频展示&#xff1a;Qt -音乐播放器(仿网易云)V1.0 前言 本文的目标&#xff1a; 一是设置任务栏的图标&#xff0c; 二…...

国产 BIM 软件万翼斗拱的技术突破与现实差距 —— 在创新与迭代中寻找破局之路

万翼斗拱在国产BIM领域迈出重要一步&#xff0c;凭借二三维一体化、参数化建模及AI辅助设计等功能形成差异化竞争力&#xff0c;在住宅设计场景中展现效率优势&#xff0c;但与国际主流软件相比&#xff0c;在功能完整性、性能稳定性和生态成熟度上仍有显著差距&#xff0c;需通…...

记录算法笔记(2025.5.29)最小栈

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int get…...

Android SurfaceFlinger核心工作机制

SurfaceFlinger 核心工作机制解析 1. 启动入口与初始化流程 (1) 进程启动入口 二进制文件&#xff1a;/system/bin/surfaceflinger 源码路径&#xff1a;frameworks/native/services/surfaceflinger/main_surfaceflinger.cppint main(int, char**) {// 1. 初始化进程配置sig…...

Golang|etcd服务注册与发现 策略模式

etcd 是一个开源的 分布式键值存储系统&#xff08;Key-Value Store&#xff09;&#xff0c;主要用于配置共享和服务发现。 ETCD是一个键值&#xff08;KV&#xff09;数据库&#xff0c;类似于Redis&#xff0c;支持分布式集群。ETCD也可以看作是一个分布式文件系统&#xff…...

深度解析UniApp盲盒系统开发:从源码架构到多端部署全流程

​一、正版盲盒系统的技术选型与源码设计​ ​跨平台开发框架的核心配置​ ​UniApp多端适配方案​ 环境搭建&#xff1a;全局安装vue/cli与npm install -g dcloudio/uni-cli&#xff0c;通过uni -V验证版本&#xff08;需≥3.0&#xff09;。多端编译命令&#xff1a; # 编译微…...

STM32的OLED显示程序亲测可用:适用于多种场景的稳定显示解决方案

STM32的OLED显示程序亲测可用&#xff1a;适用于多种场景的稳定显示解决方案 【下载地址】STM32的OLED显示程序亲测可用 这是一套专为STM32设计的OLED显示程序&#xff0c;经过实际测试&#xff0c;运行稳定可靠。支持多种OLED屏幕尺寸和类型&#xff0c;提供丰富的显示效果&am…...

【AI News | 20250529】每日AI进展

AI Repos 1、WebAgent 阿里巴巴通义实验室近日发布了WebDancer&#xff0c;一款旨在实现自主信息搜索的原生智能体搜索推理模型。WebDancer采用ReAct框架&#xff0c;通过分阶段训练范式&#xff0c;包括浏览数据构建、轨迹采样、监督微调和强化学习&#xff0c;赋予智能体自主…...

Day12 - 计算机网络 - HTTP

HTTP常用状态码及含义&#xff1f; 301和302区别&#xff1f; 301&#xff1a;永久性移动&#xff0c;请求的资源已被永久移动到新位置。服务器返回此响应时&#xff0c;会返回新的资源地址。302&#xff1a;临时性性移动&#xff0c;服务器从另外的地址响应资源&#xff0c;但…...

Linux驱动学习笔记(十)

热插拔 1.热插拔&#xff1a;就是带电插拔&#xff0c;即允许用户在不关闭系统&#xff0c;不切断电源的情况下拆卸或安装硬盘&#xff0c;板卡等设备。热插拔是内核和用户空间之间&#xff0c;通过调用用户空间程序实现交互来实现的&#xff0c;当内核发生了某种热拔插事件时…...

如何优化Elasticsearch的搜索性能?

优化 Elasticsearch 的搜索性能需要从索引设计、查询优化、硬件配置和集群调优等多方面入手。以下是系统化的优化策略和实操建议: 一、索引设计优化 1. 合理设置分片数 分片大小:单个分片建议 10-50GB(超过50GB会影响查询性能)。分片数量: 总分片数 ≤ 节点数 1000(避免…...

TI dsp FSI (快速串行接口)

简介 快速串行接口&#xff08;FSI - Fast Serial Interface &#xff09;模块是一种串行通信外设&#xff0c;能够在隔离设备之间实现可靠的高速通信。在两个没有共同电源和接地连接的电子电路必须交换信息的情况下&#xff0c;电气隔离设备被使用。 虽然隔离设备促进了信号通…...

责任链模式:构建灵活可扩展的请求处理体系(Java 实现详解)

一、责任链模式核心概念解析 &#xff08;一&#xff09;模式定义与本质 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心思想是将多个处理者对象连成一条链&#xff0c;并沿着这条链传递请求&#xff0c;直到有某…...

nlp中的频率就是权重吗

&#x1f522; 一、“频率”是什么&#xff1f; 在 NLP 中&#xff0c;**词频&#xff08;frequency&#xff09;**通常指的是&#xff1a; 某个单词或 token 在语料库中出现的次数&#xff08;或比例&#xff09; 举例&#xff1a; "The cat sat on the mat. The cat i…...

融智学“新五常”框架:五维方式的重构与协同

融智学“新五常”框架&#xff1a;五维方式的重构与协同 一、理论基底&#xff1a;从传统老五常到当代新五常的范式跃迁 邹晓辉教授提出的新五常&#xff08;生活方式DBA、学习方式DBA、工作方式DBA、旅行方式DBA、娱乐方式DBA&#xff09;&#xff0c;本质是将融智学的核心原…...