SSE 和 WebSocket 应用

news2024/11/25 9:51:57

SSE 和 WebSocket 应用

  • 一.SSE 和 WebSocket 对比
  • 二.SSE 和 WebSocket 调试
    • SpringBoot 下 SSE 应用
      • 1.依赖
      • 2.启动类
      • 3.接口类
      • 4.Html 测试
      • 5.测试结果
    • SpringBoot 下 WebSocket 应用
      • 1.依赖
      • 2.启动类
      • 3.WS 切点配置
      • 4.WS连接类配置
      • 5.WS Html 测试
      • 6.测试结果

一.SSE 和 WebSocket 对比

SSE 全称 Server-Send Events 基于 HTTP 的单向通信协议
WebSocket 基于 HTTP 封装的 WS 双向通信协议

二.SSE 和 WebSocket 调试

JVM 版本

在这里插入图片描述

SpringBoot 下 SSE 应用

1.依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>socket-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>20</maven.compiler.source>
        <maven.compiler.target>20</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>3.1.3</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>

    </dependencies>


</project>

2.启动类

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Administrator
 */
@SpringBootApplication
public class SocketDemoApp {
    public static void main(String[] args) {
        SpringApplication.run(SocketDemoApp.class,args);
    }
}

3.接口类

package org.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author zhuwd && moon
 * @Description
 * @create 2023-09-04 22:53
 */
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 线程副本变量
     */
    private ThreadLocal<AtomicReference<Boolean>> isSendThreadLocal = new ThreadLocal<>();

    /**
     * 缓存线程
     */
    private Map<Integer,AtomicReference<Boolean>> sidThreadMap = new ConcurrentHashMap<>();

    /**
     * Server Send Event
     *
     * @param type 1 温度 2 湿度
     * @return
     */
    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public synchronized SseEmitter sendToClient(int sid,int type){
        SseEmitter emitter = new SseEmitter(1000000L);
        // 模拟生成实时股票价格并推送给客户端
        Random random = new Random();
        new Thread(() -> {
            try {
                //缓存当前线程
                AtomicReference temp = sidThreadMap.remove(sid);
                if (null != temp){
                    temp.set(false);
                }
                //添加缓存
                sidThreadMap.put(sid,new AtomicReference<>(false));
                //缓存状态
                isSendThreadLocal.set(sidThreadMap.get(sid));
                //发数
                while (true) {
                    try {
                        // 生成随机值
                        double val = 10 + random.nextDouble() * 10;
                        // 获取单位
                        String unit = type == 1 ? "℃":(type == 2?"%":"");
                        // 构造股票价格的消息
                        String message = String.format("%.2f", val) + unit;
                        // 发送消息给客户端
                        emitter.send(SseEmitter.event().data(message));
                        // 休眠 1 秒钟
                        Thread.sleep(1000);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        //判断是否退出
                        if (isSendThreadLocal.get().get()){
                            isSendThreadLocal.remove();
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        }).start();
        //返回
        return emitter;
    }


    /**
     * Stop Server Send Event
     * @param sid
     */
    @GetMapping("/stop")
    public void stop(int sid){
        //取出线程
        AtomicReference<Boolean> temp = sidThreadMap.remove(sid);
        if (null != temp){
            temp.set(true);
        }
    }

}

4.Html 测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>温湿度监控</title>
</head>
<body>
<h1>温度</h1>
<h3><div id="temperature"></div></h3>
<h1>湿度</h1>
<h3><div id="humidity"></div></h3>

<br/>
<button onclick="stop(1,'temperature')">停止温度监控</button>
<button onclick="stop(2,'humidity')">停止湿度监控</button>
<button onclick="start(1)">恢复温度监控</button>
<button onclick="start(2)">恢复湿度监控</button>
<!-- <button οnclick="console.log(map.get(1).readyState)">恢复湿度监控</button> -->


<script src="https://code.jquery.com/jquery-3.6.1.js"></script>
<script>

  const map = new Map();

  //EventSource 事件 onopen/onmessage/onerror

  function getSSE(sid,type,id){
    obj = new EventSource('http://127.0.0.1:8080/test/sse?sid=' + sid + '&type=' + type);
    obj.onmessage = function (event) {
      document.getElementById(id).innerHTML = event.data;
    };

    document.getElementById(id).style.color = "#00FA9A";//"#ff0000";

    obj.onopen = function (){
      console.log('obj opopen connect obj state:' + obj.readyState)
    }

    obj.onerror = function (){
      obj.close();
      console.log('obj onerror connect obj state:' + obj.readyState)
    }

    map.set(sid,obj)
  }

  function init() {
    getSSE(1,1,'temperature')
    getSSE(2,2,'humidity')
  }

  init();

  /**
   * 停止
   */
  function stop(sid,id){
    $.get("http://127.0.0.1:8080/test/stop?sid=" + sid,function(data,status){
      console.log("Data: " + data + "\nStatus: " + status);
      document.getElementById(id).style.color = "#ff0000";
      map.get(sid).close();
    });
  }

  /**
   * 开始
   */
  function start(sid){
    status = map.get(sid).readyState
    if (status == 1) {
      console.log('see is on connected...' + sid + ' status ' + status)
      return
    }


    if (sid == 1) {
      getSSE(1,1,'temperature')
    } else if (sid == 2) {
      getSSE(2,2,'humidity')
    }
  }

</script>
</body>
</html>

5.测试结果

在这里插入图片描述

SpringBoot 下 WebSocket 应用

1.依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>socket-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>20</maven.compiler.source>
        <maven.compiler.target>20</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>3.1.3</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>${spring.version}</version>
        </dependency>

    </dependencies>


</project>

2.启动类

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Administrator
 */
@SpringBootApplication
public class SocketDemoApp {
    public static void main(String[] args) {
        SpringApplication.run(SocketDemoApp.class,args);
    }
}

3.WS 切点配置

package org.example.config;

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

/**
 * @author zhuwd && moon
 * @Description
 * @create 2023-09-04 23:15
 */
@Configuration
public class WebSocketConfig {

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

4.WS连接类配置

package org.example.config;

import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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

/**
 * @author zhuwd && moon
 *
 * 接口路径 ws://localhost:8080/webSocket/{sid};
 *
 * @Description
 * @create 2023-09-04 23:16
 */

@Slf4j
@Component
@ServerEndpoint("/websocket/{sid}")
public class MyWebSocket {

    /**
     * ws 会话
     */
    private Session session;

    /**
     * 连接 id
     */
    private String sid;

    /**
     * 缓存类对象
     */
    private static CopyOnWriteArraySet<MyWebSocket> webSockets = new CopyOnWriteArraySet<>();

    /**
     * 缓存用户信息
     */
    private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<>();

    /**
     * 缓存用户信息
     */
    private static ConcurrentHashMap<Session,String> sidPool = new ConcurrentHashMap<>();

    /**
     * 链接成功调用的方法
     * @param session
     * @param sid
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="sid") String sid) {
        try {
            this.session = session;
            this.sid = sid;
            webSockets.add(this);
            sessionPool.put(sid, session);
            sidPool.put(session,sid);
            log.info("one client join in sid {} all counts {}",sid,webSockets.size());
        } catch (Exception e) {
            log.error("join in error:",e);
        }
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.sid);
            log.info("one client leave sid {} all counts {}",sid,webSockets.size());
        } catch (Exception e) {
            log.error("leave error:",e);
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(Session session,String message) {
        //发送
        sendOneMessage(sid,"Hello " + message);
        log.info("client sid {} message : {}",sidPool.get(session),message);
    }

    /** 发送错误时的处理
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("ws sid {} error:",sidPool.get(session),error);
    }

    /**
     * 广播
     * @param message
     */
    public void sendAllMessage(String message) {
        log.info("broadcast : {}",message);
        for(MyWebSocket webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                   webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 单点发送
     * @param sid
     * @param message
     */
    public void sendOneMessage(String sid, String message) {
        Session session = sessionPool.get(sid);
        if (session != null && session.isOpen()) {
            try {
                log.info("to one : {}",message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 多点发送
     * @param sids
     * @param message
     */
    public void sendMoreMessage(String[] sids, String message) {
        for(String sid:sids) {
            Session session = sessionPool.get(sid);
            if (session != null&&session.isOpen()) {
                try {
                    log.info("to more : {}",message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.WS Html 测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WS</title>
</head>
<body>
<h1>Message</h1>
<h3><div><input id="msg" type="text" /></div></h3>
<h1>Receive</h1>
<h3><div><input id="rsg" type="text" readonly = true/></div></h3>
<br/>

<button onclick="send()">发送</button>


<script src="https://code.jquery.com/jquery-3.6.1.js"></script>
<script>

  var socket = new WebSocket('ws://127.0.0.1:8080/websocket/1');

  socket.onopen = function(evt){

  };

  socket.onerror = function(evt){

  };

  socket.onmessage = function(evt){
    console.log('------' + evt.data)
    $('#rsg').val(evt.data)
  };

  socket.onclose = function(evt){

  };

  function send(){
    socket.send($('#msg').val());
  }

</script>
</body>
</html>

6.测试结果

在这里插入图片描述

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

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

相关文章

数据结构与算法-选择冒泡快排

一&#xff1a;选择排序 场景&#xff1a;找出一个班上身高最高的人你会怎么找&#xff1f;A B C D A B 选择排序的思路和插入排序非常相似&#xff0c;也分已排序和未排序区间。但选择排序每次会从未排序区间中找到最小的元素&#xff0c;将其放到已排序区间的末尾。但是不像插…

浅谈硬件连通性测试方法有哪些

硬件连通性测试是一种用于验证硬件设备之间连接的稳定性和可靠性的测试过程。那么&#xff0c;硬件连通性测试方法有哪些呢?下面&#xff0c;就来看看具体介绍吧! 1、电气测试&#xff1a;电气测试用于检查硬件设备之间的电源和信号连接。这包括使用万用表或示波器测量电压、电…

Java开发之Redis(面试篇 持续更新)

文章目录 前言一、redis使用场景1. 知识分布2. 缓存穿透① 问题引入② 举例说明③ 解决方案④ 实战面试 3. 缓存击穿① 问题引入② 举例说明③ 解决方案④ 实战面试 4. 缓存雪崩① 问题引入② 举例说明③ 解决方案④ 实战面试 5. 缓存-双写一致性① 问题引入② 举例说明③ 解决…

面试设计模式-责任链模式

一 责任链模式 1.1 概述 在进行请假申请&#xff0c;财务报销申请&#xff0c;需要走部门领导审批&#xff0c;技术总监审批&#xff0c;大领导审批等判断环节。存在请求方和接收方耦合性太强&#xff0c;代码会比较臃肿&#xff0c;不利于扩展和维护。 1.2 责任链模式 针对…

uboot命令解析流程

uboot命令解析: (1)bootdelay没有打断,跑的是autoboot_command abortboot —>run_command_list (bootcmd) (2)否则走的cli_loop cli_loop –>cli_simple_loop ----> cli_readline —>run_command_repeatable -----> &#xff08;解析命令 匹配命令 运行命令 ) …

lv3 嵌入式开发-8 linux shell脚本函数

目录 1 函数的定义 2 函数的调用 3 变量的作用域 4 练习 1 函数的定义 基本语法&#xff1a; function name() {statements[return value] }function是 Shell 中的关键字&#xff0c;专门用来定义函数&#xff1b; name是函数名&#xff1b; statements是函数要执行…

java八股文面试[数据库]——自适应哈希索引

自适应Hash索引&#xff08;Adatptive Hash Index&#xff0c;内部简称AHI&#xff09;是InnoDB的三大特性之一&#xff0c;还有两个是 Buffer Pool简称BP、双写缓冲区&#xff08;Doublewrite Buffer&#xff09;。 1、自适应即我们不需要自己处理&#xff0c;当InnoDB引擎根…

FOXBORO FBM232 P0926GW 自动化控制模块

Foxboro FBM232 P0926GW 是 Foxboro&#xff08;福克斯博罗&#xff09;自动化控制系统的一部分&#xff0c;通常用于监测和控制工业过程。以下是关于这种类型的自动化控制模块可能具有的一些常见功能&#xff1a; 数字输入通道&#xff1a; FBM232 P0926GW 控制模块通常具有多…

2、在Windows 10中安装和配置 PostgreSQL 15.4

一、PostgreSQL 安装前简介 PostgreSQL&#xff08;通常简称为PG SQL&#xff09;是一个强大、开源的关系型数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;它具有广泛的功能和可扩展性&#xff0c;被广泛用于企业和开发项目中,PostgreSQL 具有如下一些关键特点&…

VUE3+TS项目无法找到模块“../version/version.js”的声明文件

问题描述 在导入 ../version/version.js 文件时&#xff0c;提示无法找到模块 解决方法 将version.js改为version.ts可以正常导入 注意&#xff0c;因为version.js是我自己写的模块&#xff0c;我可以直接该没有关系&#xff0c;但是如果是引入的其他的第三方包&#xff0c…

Windows系统的桌面显示信息工具___BGInfo使用

一、BGInfo简介 BGInfo(桌面显示信息工具)是微软开发的用于在Windows系统中实现将Windows系统信息【如:当前用户名、CPU、操作系统版本、IP地址、硬盘等】或自定的内容显示在桌面壁纸上的操作工具,用户可以根据自己的需要定制属于自己的桌面内容(特别是对应企业来说通过域…

原理之Thread与Runnable的关系

原理之Thread与Runnable的关系 附录 课程 附录 1.Thread和Runnable的关系

深入实现 MyBatis 底层机制的任务阶段4 - 开发 Mapper 接口和 Mapper.xml

&#x1f600;前言 在我们的自定义 MyBatis 底层机制实现过程中&#xff0c;我们已经深入研究了多个任务阶段&#xff0c;包括配置文件的读取、数据库连接的建立、执行器的编写&#xff0c;以及 SqlSession 的封装。每个任务阶段都为我们揭示了 MyBatis 内部工作原理的一部分&a…

机器学习算法基础--批量随机梯度下降法回归法

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 本节算法是梯度下降方法的小批量随机梯度下降法,算法的思路是从数中随机取出n个数据进行数梯度下降,再进行相应的迭代, 最后能够获得一个效果不错的回归方程/最优解. 算法的公式就不…

lv3 嵌入式开发-9 linux TFTP服务器搭建及使用

目录 1 TFTP服务器的介绍 2 TFTP文件传输的特点 3 TFTP服务器的适用场景 4 配置介绍 4.1 配置步骤 4.2 使用 5 常见错误 1 TFTP服务器的介绍 TFTP&#xff08;Trivial File Transfer Protocol&#xff09;即简单文件传输协议 是TCP/IP协议族中的一个用来在客户机与服务器…

9.3.3网络原理(网络层IP)

一.报文: 1.4位版本号:IPv4和IPv6(其它可能是实验室版本). 2.4位首部长度:和TCP一样,可变长,带选项,单位是4字节. 3.8位服务类型 4.16位总长度:IP报头 IP载荷 传输层是不知道载荷长度的,需要网络层来计算. IP报文 - IP报头 IP载荷 TCP报文 TCP载荷 IP载荷(TCP报文) …

解决方案|电子签加速证券业“数字革命”

电子签在各行各业中的快速普及已成为近年来的新趋势。与此同时&#xff0c;电子签也在证券行业中掀起一场数字化转型的新“革命”。 2020年10月&#xff0c; 中基协《私募投资基金电子合同业务管理办法&#xff08;试行&#xff09;&#xff08;征求意见稿&#xff09;》明确了…

《代码随想录》刷题笔记——链表篇【java实现】

链表节点定义 public class ListNode {// 结点的值int val;// 下一个结点ListNode next;// 节点的构造函数(无参)public ListNode() {}// 节点的构造函数(有一个参数)public ListNode(int val) {this.val val;}// 节点的构造函数(有两个参数)public ListNode(int val, ListNo…

【80天学习完《深入理解计算机系统》】第十四天 复习第三章

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

层次分析法(matlab实现)

1.层次分析法&#xff08;AHP&#xff09; 在决策理论中&#xff0c;层次分析法是一种以数学和心理学为基础&#xff0c;组织和分析复杂决策的结构化技术&#xff0c;它代表了一种量化决策标准权重的准确方法&#xff0c;通过成对比较&#xff0c;利用个别专家的经验来估计因素…