SpringBoot整合WebSocket详细教程

news2025/1/24 5:38:55

预期效果

在这里插入图片描述

在这里插入图片描述
共开启两个页面,实现一对一聊天。

服务端代码:https://gitee.com/lianaozhe/springboot-websocket.git

代码实现逻辑

服务端

导入相关依赖:

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

        <!-- hutool 工具包,各种封装功能 一应俱全-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>

WebSocketConfig配置类:

package com.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @ClassName WebSocketConfig
 * @Description TODO
 * @Author laz
 * @Date 2023/4/26 16:18
 * @Version 1.0
 */
@Configuration
public class WebSocketConfig {

    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    
}

WebSocket操作类:

package com.web.websocket;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @ClassName WebSocket
 * @Description TODO
 * @Author laz
 * @Date 2023/4/26 16:20
 * @Version 1.0
 */
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    /**
     * 用户ID
     */
    private String userId;

    /**
     * 用来存放每个客户端对应的MyWebSocket对象。
     */
    private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
    // 用来存在线连接用户信息
    private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userId")String userId) {
        try {
            this.session = session;
            this.userId = userId;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info("【websocket消息】有新的连接,用户id是【{}】总数为【{}】",userId,webSockets.size());
        } catch (Exception e) {
        }
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("【websocket消息】连接断开,总数为:"+webSockets.size());
        } catch (Exception e) {
        }
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息:"+message);
        JSONObject jsonObject = JSONUtil.parseObj(message);
        Object toUserId = jsonObject.get("toUserId");
        Session session = sessionPool.get(toUserId);

        session.getAsyncRemote().sendText(JSONUtil.toJsonStr(jsonObject.get("msg")));

    }

    /** 发送错误时的处理
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {

        log.error("用户错误,原因:"+error.getMessage());
        error.printStackTrace();
    }


    // 此为广播消息
    public void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:"+message);
        for(WebSocket webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);

        if (session != null&&session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:"+message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息(多人)
    public void sendMoreMessage(String[] userIds, String message) {
        for(String userId:userIds) {
            Session session = sessionPool.get(userId);
            if (session != null&&session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:"+message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

TestController测试接口类:

package com.web.controller;

import com.web.websocket.WebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName TestController
 * @Description TODO
 * @Author laz
 * @Date 2023/4/26 16:23
 * @Version 1.0
 */
@RestController
@RequestMapping("web")
public class TestController {

    @Autowired
    private WebSocket webSocket;

    @RequestMapping("test")
    public String test(String userId,String msg){
        webSocket.sendOneMessage(userId,msg);
        return "成功";
    }
}

客户端

test.html文件:

<!doctype html>
<html lang="zh-CN">
	<head>
		<!-- 必须的 meta 标签 -->
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

		<!-- Bootstrap 的 CSS 文件 -->
		<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
			integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">



		<title>WebSocket1</title>

		<style>
			form {
				width: 820px;
				margin: auto;
			}

			.div1 {
				width: 820px;
				height: 500px;
				border: #000000;
				border-width: thin;
				border-style: solid;
			}

			.div2 {
				text-align: center;
				width: 800px;
				height: 50px;
			}
		</style>

		<script>
			function clearData() {
				$("#div1").html("");
			}


			var socket;

			function openSocket() {
				if (typeof(WebSocket) == "undefined") {
					console.log("您的浏览器不支持WebSocket");
				} else {
					console.log("您的浏览器支持WebSocket");

					//websocket地址
					var socketUrl = "ws://localhost:8088/websocket/20";
					console.log(socketUrl);
					if (socket != null) {
						socket.close();
						socket = null;
					}
					socket = new WebSocket(socketUrl);
					//打开事件
					socket.onopen = function() {
						console.log("websocket已打开");
						$("#div1").append("websocket已打开" + "<br \>");
					};
					//获得消息事件
					socket.onmessage = function(msg) {
						console.log(msg.data);
						$("#div1").append("接收到消息:" + msg.data + "<br \>");

					};
					//关闭事件
					socket.onclose = function() {
						console.log("websocket已关闭");
						$("#div1").append("websocket已关闭" + "<br \>");
					};
					//发生了错误事件
					socket.onerror = function() {
						console.log("websocket发生了错误");
						$("#div1").append("websocket发生了错误" + "<br \>");
					}
				}
			}

			function sendMsg() {
				if (typeof(WebSocket) == "undefined") {
					console.log("您的浏览器不支持WebSocket");
				} else {
					console.log("您的浏览器支持WebSocket");
					console.log('{"toUserId":"' + $("#toUserId").val() + '","msg":"' + $("#msg").val() + '"}');
					socket.send('{"toUserId":"' + $("#toUserId").val() + '","msg":"' + $("#msg").val() + '"}');

					var msg = document.getElementById("msg").value;
					$("#div1").append("发送消息:" + msg + "<br \>");
					document.getElementById("msg").value = "";
				}


			}
		</script>

	</head>
	<body>

		<h1 align="center">WebSocket测试Demo</h1><br />
		<form class="form-inline">

			<div class="form-group mx-sm-3 mb-2">
				<label for="toUserId" class="sr-only">toUserId</label>
				<input type="text" class="form-control" id="toUserId" placeholder="toUserId">
			</div>
			<div class="form-group mb-2">
				<label for="msg" class="sr-only">消息內容</label>
				<input type="text" class="form-control" id="msg" placeholder="消息內容">
			</div>
			<div class="form-group mx-sm-3 mb-2">
				<button type="button" class="btn btn-primary" onclick="openSocket()">开启socket</button>
			</div>
			<div class="form-group mx-sm-3 mb-2">
				<button type="button" class="btn btn-danger" onclick="sendMsg()">发送消息</button>
			</div>
			<div class="form-group mx-sm-3 mb-2">
				<button type="button" class="btn btn-secondary" onclick="clearData()">清空内容</button>
			</div>
			<div class="div2"></div>
			<div class="div1" id="div1"></div>

		</form>

	</body>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
		integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous">
	</script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
		integrity="sha384-7ymO4nGrkm372HoSbq1OY2DP4pEZnMiA+E0F3zPr+JQQtQ82gQ1HPY3QIVtztVua" crossorigin="anonymous">
	</script>

</html>

在这里插入图片描述
复制test.html文件为test2.html文件,将上面的userId由’20’改为’10’,后面测试使用。

测试

测试一

运行服务端项目,打开test.html,点击’开启socket’按钮(一定要先开启socket连接,否则无法接收到消息)。

在这里插入图片描述

可以看到,服务端与客户端已建立了连接。

然后调用我们的测试接口:

在这里插入图片描述

然后观察test.html页面:

在这里插入图片描述
可以看到,客户端的用户20成功接收到服务端发来的信息。

测试二

打开test2.html,同样点击’开启socket’,与服务端建立连接。

此时用test2.html给test.html发送一条消息。

在这里插入图片描述
在这里插入图片描述

可以看到,此时两个页面直接已经可以进行一对一的聊天了。

代码已上传到git:https://gitee.com/lianaozhe/springboot-websocket.git

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Silane-PEG-FITC 硅烷聚乙二醇荧光素FITC-PEG-Silane在医疗设备领域有广泛应用,具有很好的生物相容性。

FITC-PEG-Silane&#xff0c;荧光素-聚乙二醇-硅烷 中文名称&#xff1a;荧光素-聚乙二醇-硅烷 英文名称&#xff1a;FITC-PEG-Silane 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 性状 :白色、微黄色粉末或固体&#xff0c;取决于分子量 溶剂&#xff1a;…

新版android studio gradle插件7.4.2.pom一直无法下载问题

android studio同步时候出现org.gradle.api.plugins.UnknownPluginException&#xff0c;Plugin [id: com.android.application, version: 7.4.2] was not found in any of the following sources: pom插件一直无法下载&#xff0c;搞了好几天&#xff0c;简直想砸电脑&#x…

USB转串口芯片CH340与CH341使用问题汇总

USB转串口&#xff1a; CH340C/N/K/E/B/G/T CH341F/B/C/T/A USB转打印口&#xff1a; CH340H/S CH341F/B/A USB转I2C&#xff1a;CH341F/B/C/T/A USB转SPI&#xff1a;CH341F/B/A/H USB转并口/GPIO&#xff1a;CH341F/B/A 芯片供电注意事项 CH340/CH341等 USB 芯片都支…

定时器按键消抖

用到按键就要处理因为机械结构带来的按键抖动问题&#xff0c;也就是按键消抖。 按键消抖原理&#xff1a;在按键按下以后延时一段时间再去读取按键值&#xff0c;如果此时按键值还有效就表示这是一次有效的按键&#xff0c;中间的延时就是消抖的。 但是&#xff0c;延时函数…

《梦幻西游H5》L inux搭建游戏服务器!

西游H5 linux 架设教程 准备工具&#xff1a; Termux&#xff08;安卓&#xff09; Notepad SecureCRT WinSCP 安装宝塔直接运行命令即可。 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.s…

在 Ubuntu 18.04 中配置静态 IP 及灵活切换

Ubuntu 18.04 使用 Netplan 来配置 IP 地址&#xff0c;Netplan 是一个新的命令行网络配置工具。 新的 IP 配置方法不会再用到 /etc/network/interfaces 这个文件&#xff0c;取而代之的是一个 YAML 文件。 默认的 Netplan 配置文件一般在 /etc/netplan 目录下。 查看当前 i…

opencv_04条形码区域分割

基于OpenCV的条形码区域分割 要基于OpenCV实现条形码区域分割&#xff0c;可以按照以下步骤进行&#xff1a; 加载图像&#xff1a;使用OpenCV中的imread函数读取待处理图像。灰度化&#xff1a;使用OpenCV中的cvtColor函数将彩色图像转换为灰度图像。边缘检测&#xff1a;使用…

java-android面试-线程状态

开篇一张图&#xff08;本图来源于网络&#xff09;&#xff0c;余下全靠编&#xff0c;我们正式开始。 一、线程的状态 总共有6种状态&#xff0c;分别如下&#xff1a; 1. 新建状态(New): 线程对象被创建后&#xff0c;就进入了新建状态。例如&#xff0c;Thread thread …

ShareSDK Google平台注册指南

注册开发者账号 访问Firebase&#xff0c;使用Google账号登录 创建项目 登录完成后&#xff0c;点击转到控制台 点击添加项目 输入项目名称&#xff0c;点击继续 根据需要选择是否开启Google Analytics&#xff08;分析&#xff09;&#xff0c;点击继续 选择Google Analytic…

04 【Sass语法介绍-运算】

1.前言 运算是一种通过已知量可能的组合&#xff0c;获得新的量的行为。Sass 中也为我们提供了各种各样的运算&#xff0c;以便我们更好的处理样式代码。本节我们将学习 Sass 中的数字运算、关系运算、除法运算、颜色运算、字符串运算等等… 2.什么是 Sass 运算&#xff1f; …

ArcGIS Pro坐标系统

目录 1 测量学基础 1.1 地球的形状和大小 1.1.1 大地水准面 1.1.2 大地体 1.1.3 地球椭球 2 坐标系统 2.1 地理坐标系 2.2 投影坐标系 2.3 局部坐标系&#xff08;假定坐标系&#xff09; 2.4 高程坐标系 3 地图投影 3.1 投影概念 3.2 投影分类 3.3 投影类型 3.…

长按实现安卓端下载图片和安装包

①长按按钮保存图片至安卓手机相册 using System.Collections; using System.Collections.Generic; using UnityEngine;public class LongPress : MonoBehaviour {//设置变量记录触摸时间与bool触摸private float touchTime;private bool newTouch = false;void Update () {//判…

在线问诊小程序系统方案以及价值

方案价值zlzwgz0127 1.扩大医院流量 a.预约到院 在线展示专家的介绍&#xff0c;更能彰显实力&#xff0c;吸引患者来院就医&#xff0c; 用户可选择在线问诊和预约到院 b.社区团购导流 与我们合作社区团购给医院的体检产品导流 c.专家直播导流 通过专家直播吸引潜在患者…

( 哈希表) 1. 两数之和 ——【Leetcode每日一题】

哈希表使用 O ( N ) O(N) O(N) 空间复杂度存储数据&#xff0c;并且以 O ( 1 ) O(1) O(1) 时间复杂度求解问题。 ❓1. 两数之和 难度&#xff1a;简单 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&am…

计算机网络【1】 TCP/IP

OSI七层模型与TCP/IP四层模型 首先简单说一下OSI参考模型&#xff0c;OSI将网络分为七层&#xff0c;自下而上分别是物理层、数据链路层、网络层、传输层、会话层、表示层、应用层&#xff0c;而TCP/IP体系结构则将网络分为四层&#xff0c;自下而上分别是网络接口层、网络层、…

如何冲破 GPT-4 的信息茧房?

由于大模型的成功经验&#xff0c;整个 AI 领域纷纷转向&#xff0c;要么是将模型架构换为 Transformer&#xff0c;要么是将小模型换为大模型&#xff0c;要么是将建模方式换为自监督、自回归范式等等。 GPT-4 推出后&#xff0c;各种 demo 充斥网络空间&#xff0c;写代码、…

论文投稿指南——如何让SCI论文英语更地道(细节掌握)

​ 学术论文英文写作都要符合ABC三个基本点&#xff1a;准确、简洁和清晰 (Accurate\Brief\Clear)&#xff0c;在此基础上做好必要的细节&#xff0c;可以让你的英文写作更地道化、学术化。以下建议可供各位小伙伴参考&#xff1a; 一、错误用法 ★ So/But/And/Also…等连接词…

如何判定自己适合自学编程还是报班?

首先在这里&#xff0c;不做偏向性推荐&#xff0c;主要还是看个人条件。 宝剑锋从磨砺出&#xff0c;学习本身是一件艰苦的事情。在决定之前&#xff0c;建议先按照下图问自己三个问题自我检测。 如果你还不能确定&#xff0c;自学和报班的优劣势分析&#xff0c;或许能帮你们…

Spring JDBC和事务控制

目录 Spring JDBC 和 事务控制主要内容Spring 整合 JDBC 环境构建项目添加依赖坐标添加 jdbc 配置文件编写 spring 配置文件配置数据源C3P0 数据源配置DBCP 数据源配置 模板类配置Spring JDBC 测试 &#xff08;入门&#xff09;创建指定数据库创建数据表使用 JUnit 测试JUnit …

面试题30天打卡-day14

1、线程的生命周期是什么&#xff0c;线程有几种状态&#xff0c;什么是上下文切换&#xff1f; 线程通常有五种状态&#xff1a;创建&#xff0c;就绪&#xff0c;运行、阻塞和死亡状态。 新建状态&#xff08;New&#xff09;&#xff1a;新创建了一个线程对象。就绪状态&am…