使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接

在 Web 开发中,WebSocket 为客户端和服务端之间提供了实时双向通信的能力。本篇博客介绍如何使用 Spring Boot 快速搭建一个 WebSocket 服务器,并支持多客户端的连接和消息广播。

1. WebSocket 简介

WebSocket 是 HTML5 的一种协议,提供了客户端和服务器之间的全双工通信。通过 WebSocket,客户端可以与服务器进行持续连接,不用反复建立 HTTP 请求,从而降低延迟,提升通信效率。

为什么选择 Spring Boot 实现 WebSocket?

Spring Boot 简化了 WebSocket 服务器的配置与实现,使我们可以更专注于业务逻辑开发,且配合 @ServerEndpoint 注解实现更加清晰。

2. 项目环境与依赖配置

项目环境

  • Java 版本:JDK 8+
  • Spring Boot 版本:2.x
  • WebSocket 依赖spring-boot-starter-websocket

Maven 依赖

pom.xml 文件中添加 WebSocket 的依赖:

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

3. WebSocket 服务端代码实现

在 Spring Boot 中,我们可以使用 @ServerEndpoint 注解创建 WebSocket 服务器端。以下是一个支持多客户端连接的 WebSocket 实现。

WebSocket 服务端代码解析

创建 MultiClientWebSocket 类,并实现以下功能:

  1. 记录当前在线客户端数
  2. 支持客户端连接、断开、消息接收、群发等功能
  3. 通过 @OnOpen@OnMessage@OnClose@OnError 注解处理连接的各个生命周期

完整代码如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * WebSocketConfig
 */
@Component
@ServerEndpoint("/ws/multi")
@Slf4j
public class MultiClientWebSocket {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        log.info("WebSocketConfig: serverEndpointExporter init WebSocketConfig 注入完成");
        return new ServerEndpointExporter();
    }

    /**
     * 记录当前在线连接数
     */
    private static final AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 存放所有在线的客户端
     */
    private static final Map<String, Session> onlineClients = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session) {
        onlineCount.incrementAndGet();
        onlineClients.put(session.getId(), session);
        log.info("有新连接加入:{},当前在线客户端数为:{}", session.getId(), onlineCount.get());
    }


    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        System.out.println("收到客户端消息:" + message);
        session.getBasicRemote().sendText("服务器收到消息:" + message);
    }

    @OnClose
    public void onClose(Session session) {
        onlineCount.decrementAndGet(); // 在线数减1
        onlineClients.remove(session.getId());
        log.info("有一连接关闭:{},当前在线客户端数为:{}", session.getId(), onlineCount.get());

    }

    @OnError
    public void onError(Throwable t) {
        log.error("WebSocket 连接出错{}", t.getMessage());
    }
    /**
     * 群发消息
     *
     * @param message 消息内容
     */
    public void sendMessage(String message) {
        //log.info("开始给在线的客户端{}群发消息{}",onlineClients,message);
        for (Map.Entry<String, Session> sessionEntry : onlineClients.entrySet()) {
            Session toSession = sessionEntry.getValue();
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getAsyncRemote().sendText(message);
        }
    }

}

代码解析

  • ServerEndpointExporter:Spring Boot 的 WebSocket 配置类,自动注册 @ServerEndpoint 注解声明的 WebSocket。
  • 在线客户端管理:使用 ConcurrentHashMap 存储在线客户端的 Session,通过 AtomicInteger 记录当前连接数。
  • 生命周期注解
    • @OnOpen:当客户端连接时触发,增加在线人数。
    • @OnMessage:当收到消息时触发,服务端处理消息。
    • @OnClose:当连接关闭时触发,减少在线人数。
    • @OnError:当连接出错时触发,记录错误日志。
  • 群发消息sendMessage 方法遍历所有在线客户端的 Session,实现消息广播。

4. 启动与测试

启动 Spring Boot 项目后,WebSocket 服务端地址为:ws://localhost:8080/ws/multi。可以使用 WebSocket 测试工具(例如 Apifox或浏览器控制台)测试 WebSocket 通信。

测试步骤

  1. 连接 WebSocket:客户端连接至 ws://localhost:8080/ws/multi
  2. 发送消息:客户端发送消息,服务端接收到消息后回复。
  3. 断开连接:客户端断开连接,服务端记录连接关闭信息。
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.lps.config.MultiClientWebSocket;
import com.lps.domain.R;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @ClassName: WebSocketControl
 * @Description:
 * @Author: liu
 * @Date: 2024-10-30
 * @Version: 1.0
 **/
@RestController
@RequestMapping("/webSocket")
@RequiredArgsConstructor
public class WebSocketControl {
    private final MultiClientWebSocket multiClientWebSocket;

    @GetMapping("/sendList")
    public R<String> sad() {
        //Guava依赖
        List<String> strList = Lists.newArrayList("发送", "WebSocket消息","测试");
        String jsonStr = JSONUtil.toJsonStr(strList);
        multiClientWebSocket.sendMessage(jsonStr);
        return R.ok(jsonStr);
    }
}

前端测试demo

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
    <style>
        #status {
            margin-bottom: 10px;
        }
    </style>
    <script>
        let ws;

		function connectWebSocket() {
			// 检查 WebSocket 是否已连接
			if (ws && ws.readyState === WebSocket.OPEN) {
				console.log('WebSocket 已连接,无需重复连接。');
				return;
			}

			ws = new WebSocket('ws://localhost:8080/ws/multi');

			ws.onopen = function() {
				console.log('WebSocket 连接已经建立。');
				document.getElementById('status').innerText = 'WebSocket 连接已建立。';
				ws.send('Hello, server!');
			};

			ws.onmessage = function(event) {
				console.log('收到服务器消息:', event.data);
				document.getElementById('messages').innerText += '收到消息:' + event.data + '\n';
			};

			ws.onerror = function(event) {
				console.error('WebSocket 连接出现错误:', event);
				document.getElementById('status').innerText = 'WebSocket 连接出现错误。';
			};

			ws.onclose = function() {
				console.log('WebSocket 连接已经关闭。');
				document.getElementById('status').innerText = 'WebSocket 连接已关闭。';
				ws = null; // 连接关闭后将 ws 设置为 null,以便重新连接
			};
		}


        function disconnectWebSocket() {
            if (ws) {
                ws.close();
            }
        }
    </script>
</head>
<body>
    <h1>WebSocket Demo</h1>
    <div id="status">WebSocket 连接状态</div>
    <button onclick="connectWebSocket()">连接 WebSocket</button>
    <button onclick="disconnectWebSocket()">断开 WebSocket</button>
    <pre id="messages"></pre>
</body>
</html>

5. 总结

通过以上代码示例,我们可以实现一个简单的 WebSocket 服务端,支持多客户端连接和消息广播。此 WebSocket 服务端适用于需要实时消息推送的应用场景,比如聊天室、实时通知系统等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHP常量

PHP 中的常量是指一旦定义后将不能被改变的标识符。 常量可以用const和define&#xff08;&#xff09;来定义。 PHP常量的特性 不变性: 常量一旦定义&#xff0c;其值不能改变。全局作用域: 常量在定义后&#xff0c;可以在整个脚本的任何地方使用&#xff0c;无需使用 glo…

让Erupt框架支持.vue文件做自定义页面模版

Erupt是什么&#xff1f; Erupt 是一个低代码 全栈类 框架&#xff0c;它使用 Java 注解 动态生成页面以及增、删、改、查、权限控制等后台功能。 零前端代码、零 CURD、自动建表&#xff0c;仅需 一个类文件 简洁的注解配置&#xff0c;快速开发企业级 Admin 管理后台。 提…

Echarts 图表根据屏幕大小自适应图表大小/标签文字大小

自适应图表大小 echarts多个图表大小随屏幕的大小改变自适应&#xff0c;Echarts 多图表自适应窗口大小&#xff0c;echarts随页面大小变化而变化&#xff1b; 但 Echarts 同一页面存在多个图表的时候&#xff0c;只有一个生效 只有一个图表的时候 直接用 window.onresize …

基于 Transformer 的语言模型

基于 Transformer 的语言模型 Transformer 是一类基于注意力机制&#xff08;Attention&#xff09;的模块化构建的神经网络结构。给定一个序列&#xff0c;Transformer 将一定数量的历史状态和当前状态同时输入&#xff0c;然后进行加权相加。对历史状态和当前状态进行“通盘…

Docker:容器编排 Docker Compose

Docker&#xff1a;容器编排 Docker Compose docker-composedocker-compose.ymlservicesimagecommandenvironmentnetworksvolumesportshealthcheckdepends_on 命令docker compose updocker compose down其它 docker-compose 多数情况下&#xff0c;一个服务需要依赖多个服务&a…

力扣633.平方数之和 c++

给定一个非负整数 c &#xff0c;你要判断是否存在两个整数 a 和 b&#xff0c;使得 a2 b2 c 。 示例 1&#xff1a; 输入&#xff1a;c 5 输出&#xff1a;true 解释&#xff1a;1 * 1 2 * 2 5示例 2&#xff1a; 输入&#xff1a;c 3 输出&#xff1a;false提示&…

【ESP32】ESP-IDF开发 | I2C从机接收i2c_slave_receive函数的BUG导致程序崩溃解决(idf-v5.3.1版本)

1. 问题 在调试I2C外设的demo时&#xff0c;按照官方文档的描述调用相关API&#xff0c;烧录程序后发现程序会不断崩溃&#xff0c;系统log如下。 初步分析log&#xff0c;原因是访问到了不存在的地址。一开始我以为是自己的代码问题&#xff0c;反反复复改了几次都会出现同样的…

链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)

AB链表公共元素生成链表C 设A和B是两个单链表(带头节点)&#xff0c;其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C&#xff0c;要求不破坏A、B的节点 算法思想 表A&#xff0c;B都有序&#xff0c;可从第一个元素起依次比较A、B两表的元素&#xff0c;若元…

蓝牙BLE开发——红米手机无法搜索蓝牙设备?

解决 红米手机&#xff0c;无法搜索附近蓝牙设备 具体型号当时忘记查看了&#xff0c;如果你遇到有以下选项&#xff0c;记得打开~ 设置权限

2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能

基于matlab-GUI的脉冲响应不变法实现音频滤波功能&#xff0c;输入加噪信号&#xff0c;通过巴特沃斯模拟滤波器脉冲响应不变法进行降噪。效果较好。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a;2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能…

智慧生活新标准:解锁耐能科技的潜能

『从马路上&#xff0c;看到旁边摄影机下方的牌子写着科技执法』 『开车进入停车场时&#xff0c;车牌辨识成功开启闸门』 『回到家门口前&#xff0c;进行脸部辨识解锁开门』 『手机APP弹起提醒&#xff0c;出现宝宝的画面表示正在哭泣』 上述的情景&#xff0c;你我是否很熟悉…

[VUE]框架网页开发1 本地开发环境安装

前言 其实你不要看我的文章比较长&#xff0c;但是他就是很长&#xff01;步骤其实很简单&#xff0c;主要是为新手加了很多解释&#xff01; 步骤一&#xff1a;下载并安装 Node.js 访问 Node.js 官网&#xff1a; Node.js — Download Node.js 下载 Windows 64 位版本&…

C++线程异步

本文内容来自&#xff1a; 智谱清言 《深入应用C11 代码优化与工程级应用》 std::future std::future作为异步结果的传输通道&#xff0c;可以很方便地获取线程函数的返回值。 std::future_status Ready (std::future_status::ready): 当与 std::future 对象关联的异步操作…

【Python】【数据可视化】【商务智能方法与应用】课程 作业一 飞桨AI Studio

作业说明 程序运行和题目图形相同可得90分&#xff0c;图形显示有所变化&#xff0c;美观清晰可适当加分。 import matplotlib.pyplot as plt import numpy as npx np.linspace(0, 1, 100) y1 x**2 y2 x**4plt.figure(figsize(8, 6))# yx^2 plt.plot(x, y1, -., labelyx^2,…

后端eclipse——文字样式:UEditor富文本编辑器引入

目录 1.富文本编辑器的优点 2.文件的准备 3.文件的导入 导入到项目&#xff1a; 导入到html文件&#xff1a; ​编辑 4.富文本编辑器的使用 1.富文本编辑器的优点 我们从前端写入数据库时&#xff0c;文字的样式具有局限性&#xff0c;不能存在换行&#xff0c;更改字体…

基于springboot+小程序的汽车销售管理系统(汽车4)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 ​ 1、管理员实现了首页、个人中心、管理员管理、基础数据管理、论坛管理、公告信息管理、汽车管理、用户管理、轮播图信息等。 ​ 2、用户实现了注册、登录、首页、汽车类型、论坛、购物…

江协科技STM32学习- P29 实验- 串口收发HEX数据包/文本数据包

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Quartz的使用

1.准备工作 建立Maven工程 2.引入Quartz的jar包 <dependencies><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version></dependency></dependencies>3…

黑马官网最新2024前端就业课V8.5笔记---CSS篇(2)

盒子模型 画盒子 目标:使用合适的选择器画盒子 新属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"vi…

单片机串口接收状态机STM32

单片机串口接收状态机stm32 前言 项目的芯片stm32转国产&#xff0c;国产芯片的串口DMA接收功能测试不通过&#xff0c;所以要由原本很容易配置的串口空闲中断触发DMA接收数据的方式转为串口逐字节接收的状态机接收数据 两种方式各有优劣&#xff0c;不过我的芯片已经主频跑…