springBoot集成websocket实现消息实时推送提醒

news2024/9/22 5:27:29

在浏览某些网页的时候,例如 WebQQ、京东在线客服服务、CSDN私信消息等类似的情况下,我们可以在网页上进行在线聊天,或者即时消息的收取与回复,可见,这种功能的需求由来已久,并且应用广泛,和pc端web系统待办提醒 等。

Web端 常见的消息推送实际上大多数都是模拟推送,之所以是模拟推送,是因为这种实现并不是服务器主动推送,本质依旧是客户端发起请求,服务端返回数据,起主动作用的是客户端。

可分为俩大类:

一、客户端实现

1.1短轮询

短轮询即浏览器定时向服务器发送请求,以此来更新数据的方法。如下图所示,原理就是客户端不断地向服务端发请求,如果服务端数据有更新,服务端就把数据发送回来,客户端就能接收到新数据了,

 浏览器每隔一段时间向服务器发送一次请求,请求浏览器想要的数据。严格意义上讲:短轮询不是服务器推送的消息,获取的数据也不是实时的

优点:前后端程序都很容易编写,没什么技术难度

缺点:这种方法因为需要对服务器进行持续不断的请求,就算你设置的请求间隔时间很长,但在用户访问量比较大的情况下,也很容易给服务器带来很大的压力,而且绝大部分情况下都是无效请求,浪费带宽和服务器资源,一般不会用于实际生产环境的,自己知道一下就行了。

1.2长轮询

长轮询是短轮询的一个翻版,或者叫改进版。浏览器向服务器发送一个请求看有没有数据,有数据就响应,没数据就保持该请求,知道有数据再返回。浏览器在服务器返回数据时再发送一个请求。这样浏览器就可以一直获取到最新的数据。长轮询的时间线如下图所示;在长轮询的情况下,服务器端在接到客户端请求之后,如果发现数据库中的数据并没有更新或者不符合要求,那么就不会立即响应客户端,而是 hold住这次请求,直到符合要求的数据到达或者因为超时等原因才会关闭连接,客户端在接收到新数据或者连接被关闭后,再次发起新的请求。

为了节约资源,一次长轮询的周期时间最好在 10s ~ 25s左右,长连接也是实际生产环境中,被广泛运用于实时通信的技术。

 

优点:

尽管长轮询不可能做到每一次的响应都是有用的数据,因为服务器超时或者客户端网络环境的变化,以及服务端为了更好的分配资源而自动在一个心跳周期的末尾断掉连接等原因,而导致长轮询不可能一直存在,必须要不断地进行断开和连接操作,但无论如何,相比于短轮询来说,长轮询耗费资源明显小了很多

缺点:

服务器 hold连接依旧会消耗不少的资源,特别是当连接数很大的时候,返回数据顺序无保证,难于管理维护。

1.3长连接

这种是基于 iframe 或者 script实现的,主要原理大概就是在主页面中插入一个隐藏的 iframe(script),然后这个 iframe(script)的 src属性指向服务端获取数据的接口,因为是iframe(script)是隐藏的,而且 iframe(script)的 刷新也不会导致 主页面刷新,所以可以为这个 iframe(script)设置一个定时器,让其每隔一段时间就朝服务器发送一次请求,这样就能获得服务端的最新数据了。
 

主要是在前端,一共两条 script脚本,大致左右就是在一定的时间间隔内(示例为 3s)就动态地在页面中增删一个链接为用于请求后端数据的 script脚本。

后端则返回一段字符串,这段字符串在返回前端时,有一个 callback字段调用前端的代码,类似于 jsonp的请求。

二、服务端实现

2.1webSocket

Web Sockets 的是在一个单独的持久连接上提供全双工、双向通信。在 JavaScript 中创建了 Web Socket 之后,会有一个 HTTP 请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会从 HTTP 协议升级为 Web Socket 协议。

WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
 

使用spring框架可以很容易实现websocket,这是spring实现websocket的官方教程(非常详细)地址:https://spring.io/guides/gs/messaging-stomp-websocket/
 

三、上代码

核心思想,使用rest 风格动态变化用户id,每个用户建立一次连接的session要保存到容器中,保证每个用户享有自己的session集合。

springBoot项目 集成

websocet 支持

一个用户下建立多个连接,例如:一个用户开启多个网页,每个网页都建立一个socket连接。

用户的多个页面,能收到统一的消息:

3.1、后端代码  加依赖

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

书写配置类,实例化Bean

package com.yxsd.cnooc.data.wb;

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

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

服务类

package com.yxsd.cnooc.data.service;

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.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketTest {

    private static ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocketTest>> userwebSocketMap = new ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocketTest>>();

    private static ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<String, Integer>();

    private String userId;


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

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") final String userId) {
        this.session = session;
        this.userId = userId;
        System.out.println("session:"+session);
        System.out.println("userId:"+userId);
        if (!exitUser(userId)) {
            initUserInfo(userId);
        } else {
            CopyOnWriteArraySet<WebSocketTest> webSocketTestSet = getUserSocketSet(userId);
            webSocketTestSet.add(this);
            userCountIncrease(userId);
        }
        System.out.println("有" + userId + "新连接加入!当前在线人数为" + getCurrUserCount(userId));
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        CopyOnWriteArraySet<WebSocketTest> webSocketTestSet = userwebSocketMap.get(userId);
        //从set中删除
        webSocketTestSet.remove(this);
        //在线数减1
        userCountDecrement(userId);
        System.out.println("有一连接关闭!当前在线人数为" + getCurrUserCount(userId));
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        CopyOnWriteArraySet<WebSocketTest> webSocketSet = userwebSocketMap.get(userId);
       /* System.out.println("来自客户端" + userId + "的消息:" + message);
        //群发消息
        for (WebSocketTest item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }*/
    }


    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }



    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     */

    public void sendMessage(String message) throws IOException {
        System.out.println("服务端推送" + userId + "的消息:" + message);
        this.session.getAsyncRemote().sendText(message);
    }


    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。   我是在有代办消息时 调用此接口 向指定用户发送消息 
     *
     * @param message
     * @throws IOException
     */

    public void sendMessage(String userId,String message) throws IOException {
        System.out.println("服务端推送" + userId + "的消息:" + message);
        CopyOnWriteArraySet<WebSocketTest> webSocketSet = userwebSocketMap.get(userId);
        //群发消息
        for (WebSocketTest item : webSocketSet) {
            try {
                item.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }


    public boolean exitUser(String userId) {
        return userwebSocketMap.containsKey(userId);
    }

    public CopyOnWriteArraySet<WebSocketTest> getUserSocketSet(String userId) {
        return userwebSocketMap.get(userId);
    }

    public void userCountIncrease(String userId) {
        if (count.containsKey(userId)) {
            count.put(userId, count.get(userId) + 1);
        }
    }


    public void userCountDecrement(String userId) {
        if (count.containsKey(userId)) {
            count.put(userId, count.get(userId) - 1);
        }
    }

    public void removeUserConunt(String userId) {
        count.remove(userId);
    }

    public Integer getCurrUserCount(String userId) {
        return count.get(userId);
    }

    private void initUserInfo(String userId) {
        CopyOnWriteArraySet<WebSocketTest> webSocketTestSet = new CopyOnWriteArraySet<WebSocketTest>();
        webSocketTestSet.add(this);
        userwebSocketMap.put(userId, webSocketTestSet);
        count.put(userId, 1);
    }

}


此处前端测试

客户端页面使用 http://www.jsons.cn/websocket/ 与项目建立连接,充当web客户端

启动项目

模拟俩个浏览器 用户1发送消息 到服务器     和  服务器 向用户1发送消息 俩个浏览器都能接受到消息

连接websocket

 

 

 

 

 用户1发送消息

 

 

 

模拟服务器向用户1 发消息   写个接口  调用触发发送  只贴部分代码

 

@GetMapping("/sendMsage")
public Result sendMsage(@RequestParam("userId") String userId, @RequestParam("msg") String msg){
    try {
        webSocketTest.sendMessage(userId,msg);
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("出现异常");
    }
    return ResultUtils.success("查询成功!",null);
}

http://127.0.0.1:9010/user/sendMsage?userId=1&msg=aa

用户1  俩浏览器都接受到消息

 

 

 

 

四、前端代码  参考  知其所以然

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script>
    let webSocket = null;   // 创建一个变量
    if ('WebSocket' in window){  // 判断当前的浏览器是否支持WebSocket
        // 如果支持则创建一个WebSocket赋值给刚才创建的变量
        // 后面的路径实际上就是一次请求,但是这里用的是WebSocket协议
        // 记住这个地方后面详细讲到怎么写
        webSocket = new WebSocket('ws://localhost:8080/webSocket');
    }else{  // 如果不兼容则弹框,该浏览器不支持
        alert('该浏览器不支持')
    }

    /** 
     * 当WebSocket创建连接(初始化)会触发该方法
     */
    webSocket.onopen = function (event){
        console.log('建立连接') // 这个代表在浏览器打印日志,跟Java的System.out.println()意思一致
    }

    /** 
     * 当WebSocket关闭时候会触发该方法
     */
    webSocket.onclose = function (event){
        console.log('关闭连接') // 同上
    }
    
     /** 
     * 当WebSocket接受消息会触发该方法
     */
    webSocket.onmessage = function (event){
        console.log('收到消息:'+event.data)
    }

    /** 
     * 当WebSocket连接出错触发该方法
     */
    webSocket.onerror = function (event){
        console.log('websocket发生错误');
    }
    
    /** 
     * 页面关闭,WebSocket关闭
     */
    window.onbeforeunload = function (){
        webSocket.close();
    }
</script>
</html>

参考

demo篇---同一个用户开启多个webSocket连接_茄子_土豆的博客-CSDN博客_websocket多个连接

Java版 WebSocket实现消息推送【保姆来了!】_地雷Java的博客-CSDN博客_java整合websocket实现实时推送

服务器推送消息方法总结及实现(java)_xubaodian的博客-CSDN博客

javaweb实现即时消息推送功能_飞亦浩然的博客-CSDN博客_java实时推送消息到前端

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

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

相关文章

新建anaconda使用jupyter出现的一系列问题

1&#xff0c;运行一段机器学习代码&#xff0c;报缺少h5py的错误. 使用conda install h5py1.8.0 安装无法安装&#xff0c;因为当前环境的python版本是3.9&#xff0c;只能用3.7及以下的版本。无奈只能新建一个conda 环境。 2&#xff0c;新建一个 python3.7的conda 环境。运行…

「风控算法服务平台」高性能在线推理服务设计与实现

本文作者&#xff1a;郁昌存 来自京东科技-风险管理中心 一、背景/目标 1&#xff09; 风控智能化体系建设依赖大量深度学习/机器学习模型进行实时在线的风险识别、智能决策。要求可以将算法模型快速部署为在线服务&#xff0c;供决策引擎调用。 2&#xff09; 风控决策引擎…

文献 | 教师主观幸福感变迁:横断历史研究的视角

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是莹~ 疫情带来的社会经济变化正在改变着我们的求职意向&#xff0c;越来越多的人参与到考公考编的大军中。其中&#xff0c;教师这一职业的稳定性和社会认同度吸引了越来越多的年轻人参加教资考试。 教…

Linux Top 详细介绍,包含task排序

Linux Top 当我们在终端输入 top 命令时&#xff0c;会弹出一个变化的页面&#xff0c;打印出当前系统的大量重要指标&#xff0c;以及很多进程当前的运行情况&#xff1a; 可以看到&#xff0c;top 命令主要是两部分&#xff0c;第一部分为 头部指标&#xff0c;打印的是当…

磨金石教育摄影技能干货分享|那些酷炫的照片是怎么拍出来的?

在网上我们经常会看到一些非常有创意&#xff0c;非常炫酷的照片。喜欢摄影的朋友肯定会想&#xff0c;这样的照片怎么拍呢&#xff1f;由于照片的创意度很高&#xff0c;导致很多人想模仿却不知道怎么模仿。以前学的那些构图、选景等技巧&#xff0c;好像不太够用。 今天我们…

2022年文化艺术品产权交易所研究报告

第一章 文化艺术品产权交易所发展概述 1.1 文交所概念 文化产权交易所&#xff08;简称“文交所”&#xff09;从事文化产权交易及相关投融资服务工作&#xff0c;促进文化产业要素跨行业、跨地域、跨所有制流动。文交所从事的创新业务主要是文化艺术品的份额化&#xff0c;即…

idea 配置ssm项目后配置文件的简要解析及功能类之间的联系

注&#xff1a;本文不包含怎么配置 idea ssm 项目&#xff0c;仅做个人向配置好之后&#xff0c;对于各个文件的分析及跳转之间的的浅解析&#xff08;之前照着配的文章找不到了qwq&#xff09;。 叠甲&#xff1a;新手&#xff0c;刚学&#xff0c;不太会&#xff0c;如有错请…

初识Python_数据容器_字符串str

一、再识字符串字符串---字符的容器。一个字符串可以存放任意数量的字符1、字符串的下标&#xff08;索引&#xff09;和其他容器如&#xff1a;列表、元组一样&#xff0c;字符串也可以通过下标进行访问从前向后 下标从0开始从后向前&#xff0c;下标从-1开始同元组一样&#…

11个精美网页——Web前端开发技术课程大作业,期末考试,Dreamweaver简单网页制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 精彩专栏推荐&#x1f4…

罗茨气体流量计的结构设计

目 录 摘 要 I ABSTRACT II 1绪 论 1 1.1 引言 1 1.2 罗茨气体流量计的特点 1 1.&#xff13;罗茨气体流量计的应用场合[3] 2 1.4 发展前景[5] 6 2罗茨气体流量计的工作及结构原理 7 2.1 罗茨气体流量计的工作原理[3] 7 2.2罗茨气体流量计的结构原理 7 2.2.1 罗茨气体流量计的结…

echarts——实现3D地图+3D柱状图 效果——粗糙代码记录——技能提升

最近看到同事在弄下面的这个图&#xff0c;这个图是从网上看到的&#xff0c;是某个网站的收费项目&#xff1a; 收费模板&#xff1a;&#xffe5;29.9元购买&#xff0c;且必须是高级版尊享版才能够购买这个。。。 死贵&#xff01;&#xff01;&#xff01; 所以&#xf…

多臂PEG衍生物——8-Arm PEG-N3,8-Arm PEG-Azide,八臂-PEG-叠氮

多臂PEG衍生物八臂-聚乙二醇-叠氮&#xff0c;化学试剂其英文名为8-Arm PEG-Azide&#xff0c;8-Arm PEG-N3&#xff0c;它所属分类为Azide PEG Multi-arm PEGs。 八臂PEG叠氮的分子量均可定制&#xff0c;有&#xff1a;八臂-peg 5k-叠氮、八臂PEG 2k叠氮、叠氮-聚乙二醇 10k…

19 06-读取DTC扩展数据记录

诊断协议那些事儿 诊断协议那些事儿专栏系列文章&#xff0c;19服务作为UDS中子功能最多的服务&#xff0c;一共有28种子功能&#xff0c;本文将介绍常用的19 06服务&#xff1a;读取DTC扩展数据。 关联文章&#xff1a; 19服务List 19 01-通过状态掩码读取DTC数目 19 02-检…

PL/Java的安装及使用

安装步骤 1.1 下载相应扩展包 https://network.pivotal.io/products/vmware-tanzu-greenplum#/releases/1193700 1.2 上传并安装pljava-2.0.4-gp6-rhel7_x86_64.gppkg [gpadmingp_master ~]$ gppkg -i pljava-2.0.4-gp6-rhel7_x86_64.gppkg 重启数据库 [gpadmingp_master ~]$…

企业管理中,商业智能BI主要做哪些事情?

开门见山的告诉大家&#xff0c;在企业管理中商业智能BI 主要就做三件事&#xff1a;拉通数据、整合数据、数据可视化展现。 技术角度的商业智能BI 从技术的角度来讲&#xff0c;商业智能BI是一套完整的由数据仓库、查询报表、数据分析等组成的数据类技术解决方案。它有一个非…

【计算机毕业设计】23.图书馆管理系统源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 1 绪论 1.1 开发背景 图书馆管理系统的特点是从图书管理的角度出发&#xff0c;用集中的数据库将几乎所有与图书相关的数据统一管理起来&#xff0c;形成了集成的信息源。有好的用户界面&#xff0c;强有力的报表生…

AMR论文阅读之:ATP: AMRize Then Parse! Enhancing AMR Parsing with PseudoAMRs

文章目录AbstractIntroductionMethodologyAuxiliary Task selectionSRLDPAMRizationTranform SRL to PseudoAMRConnectivity FormationArgument ReductionReentrancy RestorationDependency Guided RestorationTransform Dependency Structure to PseudoAMRRedundant Relation …

容器技术-Docker的优点

当产品运行在内部的虚拟化平台中&#xff0c;如openstack&#xff0c;也就是KVM虚拟化&#xff0c;创建虚拟机&#xff0c;但是不断增加的云端应用&#xff0c;增加了对硬件资源的消耗&#xff0c;不断的创建虚拟机&#xff0c;消耗了大量的硬件资源。 那么如何高效的利用硬件…

2022美亚个人赛复盘

个人赛加密容器解密密钥 CZDGm#&2_Ns$7wSMn%ZGr7xntcHS7d5uFta#Up9544jx_cvP$uFM7?pTDa*jN&QyFDLS8U%hx$fXN^BY$Xsj3F^y#4QFXb*UqwLmkCE7?&YpnX6shKrzpVE%v?& 案件详情 于2022年10月&#xff0c;有市民因接获伪冒快递公司的电邮&#xff0c;不慎地于匪徒…

激光雷达Velodyne16配置及录制rosbag

激光雷达Velodyne16配置一、配置IP二、安装ROS依赖三、创建工作空间四、录制bag数据包硬件准备&#xff1a;连接电源线及网线一、配置IP 1.设置有线的IP地址 设置->网络->有线连接->IPV4->手动 地址&#xff1a; 192.168.8.70 掩码&#xff1a; 255.255.255.0 网关…