SpringBoot整合WebSocket实现简易聊天室

news2025/2/28 2:52:18

文章目录

    • 什么是WebSocket ?
    • WebSocket通信模型
    • 为什么需要WebSocket
    • Websocket与http的关系
    • SpringBoot集成WebSocket

什么是WebSocket ?

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket通信模型

为什么需要WebSocket

http 通信是单向的,发送请求获取响应,没有请求也就没有响应。

简单理解:

  • HTTP 打电话:客户端问一句服务端答一句

  • WebSocket 打电话:双向对话

Websocket与http的关系

相同点:

  • 都是基于tcp的,都是可靠性传输协议

  • 都是应用层协议

不同点:

  • WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息

  • HTTP是单向的

  • WebSocket是需要浏览器和服务器握手进行建立连接的而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接。

SpringBoot集成WebSocket

maven依赖

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

WebSocketConfig

@Configuration  
public class WebSocketConfig {  
	
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
}

WebSocketService

@Slf4j
@Service
@ServerEndpoint(value = "/myService/{userId}")
public class WebSocketService {

    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String,WebSocketService> webSocketMap = new ConcurrentHashMap<>();

    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的session对象。
     */
    private static ConcurrentHashMap<String,Session> sessionMap = new ConcurrentHashMap<>();

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

    private String userId = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        if(webSocketMap.containsKey(userId) && sessionMap.containsKey(userId)){
            webSocketMap.remove(userId);
            sessionMap.remove(userId);
            sessionMap.put(userId,session);
            webSocketMap.put(userId,this);
        }else{
            webSocketMap.put(userId,this);
            sessionMap.put(userId,session);
            addOnlineCount();
        }
        log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            subOnlineCount();
        }
        log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        this.session = session;
        log.info("收到客户端消息 -> {}",message);
        //服务端收到客户端的消息并推送给客户端
        sendMessage(message);
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
       log.error(error.getMessage());
    }

    /**
     * 实现服务器主动推送   可以通过controller调用此方法实现主动推送
     */
    public void sendMessage(String message){
        try {
            Set<Map.Entry<String, Session>> entries = sessionMap.entrySet();
            for (Map.Entry<String, Session> next : entries) {
                Session session = next.getValue();
                session.getBasicRemote().sendText(this.userId + "说" + message);
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount.get();
    }

    public static synchronized void addOnlineCount() {
        WebSocketService.onlineCount.getAndIncrement();
    }

    public static synchronized void subOnlineCount() {
        WebSocketService.onlineCount.getAndDecrement();
    }
}


WebSocket.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket通讯</title>
</head>

<body>
	<p>userId:<input id="userId" name="userId" type="text" value="10"></p>
	<p>msg:<input id="contentText" name="contentText" type="text" value="hello websocket"></p>
	<p>操作:<button onclick="openSocket()">开启socket</button></p>
	<p>操作:<button onclick="sendMessage()">发送消息</button></p>
</body>

<script type="application/javascript">
    let socket;
    function openSocket() {
        if(socket != null){
            socket.close();
            socket = null;
        }
        let userId = document.getElementById('userId').value
        socket = new WebSocket("ws://localhost:9000/myService/"+userId);
        //打开事件
        socket.onopen = function() {
            console.log("websocket已打开");
        };
        //获得消息事件
        socket.onmessage = function(msg) {
            console.log(msg.data);
        };
        //关闭事件
        socket.onclose = function() {
            console.log("websocket已关闭");
        };
        //发生了错误事件
        socket.onerror = function() {
            console.log("websocket发生了错误");
        }
    }
    function sendMessage() {
        let contentText = document.getElementById('contentText').value
        socket.send(contentText);
    }
</script>
</html>

测试
在这里插入图片描述

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

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

相关文章

Opencv(C++)笔记--腐蚀与膨胀操作、创建滑动条

目录 1--膨胀操作 2--腐蚀操作 3--腐蚀和膨胀的作用 4--创建滑动条 5--实例代码 1--膨胀操作 ① 原理&#xff1a; 将图像&#xff08;原图像的一部分 A &#xff09;与核矩阵&#xff08;结构元素 B &#xff09;进行运算&#xff0c;将结构元素 B 覆盖图像 A&#xff0…

[附源码]Nodejs计算机毕业设计基于的数字图书馆系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

离散被解释变量

一、二值选择模型 采用probit或者logit模型 logit y x1 x2 ,nolog r vce(cluster clustervar) ornolog表示不用显示迭代过程vce(cluster cluster)表示运用聚类标准误&#xff0c;由于二值选择模型一般采用稳健标准误的意义不大&#xff0c;所以常常使用聚类标准误。or 表示结…

数据可视化:对比漏斗图多维度分析大学在校实际开销情况

都说80后90后是“苦逼”的一代&#xff0c;他们读小学的时候&#xff0c;上大学免费&#xff1b;等到他们上大学了&#xff0c;读小学免费。可事实真的是这样吗&#xff1f;下面小编用一款数据可视化软件&#xff0c;带你解读一下现在的大学生&#xff0c;开销到底有多少。 漏…

怎样判断一个变量是数组还是对象?

判断的基本方法 1. typeof(不可以) 通常情况下&#xff0c;我们第一时间会想到typeof运算符&#xff0c;因为typeof是专门用于类型检测的&#xff0c;但是typeof并不能满足这样的需求&#xff0c;比如 let a [7,4,1] console.log(typeof(a)) //输出object 复制代码 2. in…

以太网 VLAN的5种划分方式(基于端口、基于MAC地址、基于IP子网、基于协议、基于策略)介绍与基础配置命令

2.8.3 以太网 VLAN&#xff08;VLAN划分方式&#xff09; VLAN的划分方式有2.8.3 以太网 VLAN&#xff08;VLAN划分方式&#xff09;一、基于端口划分二、基于MAC地址划分三、基于IP子网划分四、基于协议划分五、基于策略划分一、基于端口划分 简述&#xff1a;端口上进行手动…

bitset位图的介绍与使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录bitset的介绍位图的引入位图的概念位图的应用bitset的使用bitset的定义方式bitset的成员函数bitset运算符的使用如有错误&#xff0c;多多指教&#xff01;bitset的介…

传奇GEE引擎微端架设教程

传奇GEE引擎微端架设教程 GEE引擎架设微端需要准备好微端程序&#xff0c;用网站下载在服务器的版本 Mirserver文件一般都是自带微端程序的&#xff0c;偶尔也有版本没有微端程序那我们只需要到别的版本或者资源把微端程序拉到我们的文件夹里面D&#xff1a;\Mirserver 这个就…

MyBatisPlus常用注解

MyBatisPlus常用注解 TableName&#xff1a;自定义表名 给User实体类添加注解 aplication.yml中添加mp的配置 # 配置mp的日志 mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 设置MyBatis-Plus的全局配置global-config:db-config:table…

java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(三:注册中心balabala)~整起

比如咱们作为客户端进行购物时&#xff0c;那么多服务提供者【服务提供者有很多实例&#xff0c;可能人家已经搞了拆分模块后的分布式集群&#xff0c;那实例就不少啦】&#xff0c;如果用非技术的眼光看就是&#xff0c;你提供多个&#xff0c;我挑一个买&#xff0c;咱们的访…

立足浙江 辐射全国 护航数字经济发展|美创科技亮相首届数贸会

12月11日-14日&#xff0c;首届全球数字贸易博览会在杭州隆重召开。作为国内唯一经党中央、国务院批准的以数字贸易为主题的国家级、全球性专业博览会&#xff0c;首届数贸会由浙江省人民政府和商务部联合主办&#xff0c;杭州市人民政府、浙江省商务厅和商务部贸发局共同承办。…

EtherCAT设备协议详解二、EtherCAT状态机及配置流程

EtherCAT状态机&#xff08;ESM&#xff09; EtherCAT状态机定义了每个EtherCAT从站设备的分步设置&#xff0c;并指示了可用的功能。设备可以拒绝来自主站的状态请求&#xff0c;并通过错误指示&#xff08;AL 状态寄存器中的错误标志&#xff09;和相关错误代码&#xff08;A…

Linux高级 I/O

目录 一、五种I/O模型 1. 阻塞式I/O 2. 非阻塞式I/O 3. I/O复用&#xff08;多路转接&#xff09; 4. 信号驱动式I/O 5. 异步I/O 二、五种I/O模型的比较 三、I/O复用典型使用在下列网络应用场合 一、五种I/O模型 阻塞式I/O非阻塞式I/OI/O复用&#xff08;多路转接&a…

ios 计算label宽度方法效率对比

方式1&#xff1a; 设置好label的文字&#xff0c;字号等属性后&#xff0c;调用 self.label.text "111"; self.label.font [UIFont systemFontOfSize:17]; [self.label sizeToFit]; 方式2&#xff1a; 使用字符串的方法计算文字宽度&#xff0c; - (CGRect)boun…

TheFuck—Python写的超实用命令纠正工具

序言 哈喽兄弟们&#xff0c;我们在学习Python的过程中&#xff0c;有这么一款工具&#xff0c;可以轻松纠正我们写错的命令&#xff0c;简直太好用了~ The Fuck 是一款功能强大的、Python编写的应用程序&#xff0c;可用于纠正控制台命令中的错误&#xff0c;非常强大。此外…

Linux多线程(二):线程控制

文章目录一、前言二、认识线程控制函数1.线程创建2.线程退出3.线程等待4.查看线程id5.线程分离6.综合demo三、线程id本质是地址&#xff1f;一、前言 上篇博客谈到&#xff0c;Linux并没有真线程&#xff0c;而是通过复用进程的数据结构来模拟实现线程的。因此 Linux 自然不会提…

所有的为时已晚都是恰逢其时,社科院与杜兰大学金融管理硕士邀你在职读研

最近有咨询的同学询问&#xff0c;我年龄快35岁了读研晚吗&#xff1f;记得在网上看到过一句话&#xff0c;你觉得为时已晚的时候&#xff0c;恰恰是最早的时候。你可以确定一下你的内心&#xff0c;是不是真的想读研&#xff0c;既然迟早要读的话&#xff0c;与其等到了40、50…

ISO 15765-2协议分享(一)- TP时间参数详解

文章目录 前言一、时间参数协议定义二、使用步骤 1.引入库2.读入数据总结前言 无大志者常立志,让自己生活中时时有目标,有努力的方向。 老规矩,正文前分享喜欢的文字: 生活中,你越是去竭力回避不适的感觉,就越是难以推进生活中的重要转变。 不回避,积极面对,是提升自…

蓝奥声网关为什么会受到广大用户的欢迎?

蓝奥声GP20蓝牙网关是一款支持无线和有线连接的智能网关&#xff0c;配有独特的网口驱动&#xff0c;支持带有TCP/IP的以太网接口。用于互联网访问和与主机服务器的通信&#xff0c;用于远程命令和本地处理数据的数据上传/下载。它支持 RJ45/POE 和 WiFi 两种网络角色。 GP20蓝…

点面科技荣获优胜企业奖,圆满从2022“创·在上海”国际创新创业大赛之“创·加速营”毕业

创在上海 赢在未来 “创在上海”国际创新创业大赛暨中国创新创业大赛 (上海赛区) 是一项具有广泛影响力的创新创业活动。 为提升入围全国赛的参赛企业的参赛能力&#xff0c;“创在上海”将充分整合和调动多方资源&#xff0c;结合国赛进程&#xff0c;创新推出针对入围国赛企…