springboot实现SSE之牛刀小试

文章目录

  • 一,概述
    • 1.SSE是何方神圣?
    • 2.sse与webscoket区别
  • 二,实现过程
    • 1.效果展示
    • 2. 简要流程
    • 3. 源码放送
    • 4.完整项目

一,概述

1.SSE是何方神圣?

SSE 全称Server Sent Event,直译一下就是服务器发送事件。

其最大的特点,可以简单概括为两个

  • 长连接
  • 服务端可以向客户端推送信息

2.sse与webscoket区别

sse 是单通道,只能服务端向客户端发消息;而 webscoket 是双通道

那么为什么有了 webscoket 还要搞出一个 sse 呢?既然存在,必然有着它的优越之处

ssewebsocket
http 协议独立的 websocket 协议
轻量,使用简单相对复杂
默认支持断线重连需要自己实现断线重连
文本传输二进制传输
支持自定义发送的消息类型-

二,实现过程

下面我们以springboot工程为例,实现服务器端不间断向客户端推送数据

1.效果展示

http://124.71.129.204:8080/index
在这里插入图片描述

2. 简要流程

封装SSE工具类
定义接口连接SSE
页面引用接口

3. 源码放送

SSE工具类


import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import lombok.extern.slf4j.Slf4j;/*** Server-Sent Events <BR>* https://blog.csdn.net/hhl18730252820/article/details/126244274*/
@Slf4j
public class SSEServer
{/*** 当前连接数*/private static AtomicInteger count = new AtomicInteger(0);private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();public static SseEmitter connect(){String userId = RandomStringUtils.randomAlphanumeric(10);SseEmitter sseEmitter = new SseEmitter(0L); // 设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常// 注册回调sseEmitter.onCompletion(completionCallBack(userId));sseEmitter.onError(errorCallBack(userId));sseEmitter.onTimeout(timeOutCallBack(userId));sseEmitterMap.put(userId, sseEmitter);log.info("create new sse connect ,current user:{}, count: {}", userId, count.incrementAndGet());return sseEmitter;}public static void batchSendMessage(String message){sseEmitterMap.forEach((k, v) -> {try{v.send(message, MediaType.APPLICATION_JSON);}catch (IOException e){log.error("user id:{}, send message error:{}", k, e.getMessage());removeUser(k);}});}public static void removeUser(String userId){sseEmitterMap.remove(userId);log.info("remove user id:{}, count: {}", userId, count.decrementAndGet());}public static int getUserCount(){return count.intValue();}private static Runnable completionCallBack(String userId){return () -> {log.info("结束连接,{}", userId);removeUser(userId);};}private static Runnable timeOutCallBack(String userId){return () -> {log.info("连接超时,{}", userId);removeUser(userId);};}private static Consumer<Throwable> errorCallBack(String userId){return throwable -> {log.error("连接异常,{}", userId);removeUser(userId);};}
}

sse接口


import java.util.concurrent.TimeUnit;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import com.fly.hello.service.SSEServer;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;@Slf4j
@Api(tags = "sse接口")
@RestController
@RequestMapping("/sse")
public class SSEController
{long i = -1;@ApiOperation("初始化")@GetMapping("/connect/{userId}")public SseEmitter connect(@PathVariable String userId){SseEmitter sseEmitter = SSEServer.connect();if (i < 0){new Thread(() -> sendMessage()).start();}return sseEmitter;}private void sendMessage(){if (i < 0) // 保证仅触发一次{log.info("Server-Sent Events start");while (true){try{TimeUnit.MILLISECONDS.sleep(1000);}catch (InterruptedException e){}i = ++i % 101;SSEServer.batchSendMessage(String.valueOf(i));}}}
}

页面引用sse

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link href="css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<style>
body {margin: 10;font-size: 62.5%;line-height: 1.5;
}.blue-button {background: #25A6E1;padding: 3px 20px;color: #fff;font-size: 10px;border-radius: 2px;-moz-border-radius: 2px;-webkit-border-radius: 4px;border: 1px solid #1A87B9
}table {width: 60%;
}th {background: SteelBlue;color: white;
}td, th {border: 1px solid gray;font-size: 12px;text-align: left;padding: 5px 10px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;max-width: 200px;white-space: nowrap;text-overflow: ellipsis;text-overflow: ellipsis;
}
</style>
</head>
<title>Hello World!</title>
<script>let data = new EventSource("/sse/connect/001")data.onmessage = function(event) {document.getElementById("result").innerText = event.data + '%';document.getElementById("my-progress").value = event.data;}
</script>
<body><div class="wrapper-page"><table align="center"><tr><th colspan="4">Navigate</th></tr><tr><td><a href="/index" target="_self">index</a></td><td><a href="/404" target="_self">出错页面</a></td><td><a href="/doc.html" target="_blank">doc.html</a></td><td><a href="/h2-console" target="_blank">h2-console</a></td></tr></table><div class="ex-page-content text-center"><h2 align="center"><a href="index">reload</a><div><progress style="width: 60%" id="my-progress" value="0" max="100"></progress></div><div id="result"></div></h2><img src="show/girl" width="600" height="600" /><img src="show/pic" width="600" height="600" /></div></div>
</body></html>

4.完整项目

https://gitcode.com/00fly/springboot-hello

git clone https://gitcode.com/00fly/springboot-hello.git

有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣:104. 二叉树的最大深度(Java,DFS,BFS)

目录 题目描述&#xff1a;输入&#xff1a;输出&#xff1a;代码实现&#xff1a;1.深度优先搜索&#xff08;递归&#xff09;2.广度优先搜索&#xff08;队列&#xff09; 题目描述&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从…

VSCode搭建内核源码阅读开发环境

0. 参考链接 使用VSCode进行linux内核代码阅读和开发_vscode阅读linux内核-CSDN博客 1. 搭建Linux内核源码阅读环境 现状&#xff0c;Linux内核源码比较庞大文件非常多&#xff0c;其中又包含的众多的宏定义开关配置选项&#xff0c;这使得阅读内核源代码称为一件头疼的事。 …

11.接口自动化测试-Allure报告(2)

目录 1.如何同时执行多个测试文件2.Allure的不同层级应用Allure报告&#xff1a; 1.如何同时执行多个测试文件 &#xff08;1&#xff09;新建bat文件 &#xff08;2&#xff09;写命令 cd ./testCase pytest -s --alluredir ./report --clean-alluredir allure serve ./repo…

【一竞技DOTA2】双冠王N0tail将发布新书自传《性格胜过天赋》

1、近日Ti双冠王N0tail&#xff0c;在个人推特上宣布将发布自己的自传《性格胜过天赋》。N0tail职业生涯豪夺两届Ti冠军&#xff0c;700万美元个人生涯奖金亦是电竞史最高的传奇选手。 “经过许多年的努力和收到的大量请求&#xff0c;我将发布我的自传新书《性格胜过天赋》&am…

1.PROXY-代理内容

SwitchyOmega&#xff0c;浏览器插件&#xff08;edge&#xff0c;火狐插件库都是存在的&#xff09;&#xff0c;安装后的样式 1.安装 2.设置 我们常用的功能&#xff0c;主要是设置一个代理ip访问学习网站入github等&#xff0c;或者docker服务等 3.启动代理 4.验证 https…

Java测试编程题

题目1 1.创建5个线程对象 线程名设置为&#xff08;Thread01&#xff0c;Thread02&#xff0c;Thread03&#xff0c;Thread04&#xff0c;Thread05&#xff09;使用 代码实现5个线程有序的循环打印&#xff0c;效果如下&#xff1a; Thread01正在打印1 Thread02正在打印2 Threa…

信道的题目

调制信道分为恒参信道和随参信道。恒参信道举例&#xff1a;各种有线信道&#xff1b;中长波地波传播、卫星中继。随参信道举例&#xff1a;短波电离层反射信道、各种散射信道、移动通信信道。狭义信道分为有线信道和无线信道。广义信道包含调制信道和编码信道。调制信道中不包…

Three.js--》探秘虚拟现实VR展厅的视觉盛宴

今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多说直接开始。 目录 项目搭建 初始化three代码 camera-controls控制器使用 添加画框 画框处理事件 添加机器人模型 …

强化网络安全防线,您的等级保护措施到位了吗?

在这个信息化飞速发展的时代&#xff0c;网络安全已经成为我们每个人都需要关注的问题。无论是企业还是个人&#xff0c;我们的工作和生活都越来越依赖于网络。确保网络环境的安全&#xff0c;防止信息泄露和网络攻击&#xff0c;已经成为了一项至关重要的任务。等级保护制度作…

Spring-dataSource事务案例分析-使用事务嵌套时,一个我们容易忽略的地方

场景如下&#xff1a; A_Bean 中的方法a()中调用B_Bean的b();方法都开启了事务&#xff0c;使用的默认的事务传递机制&#xff08;即&#xff1a;属于同一事务&#xff09;&#xff1b; 如下两种场景会存在较大的差异&#xff1a; 在b()方法中出现了异常&#xff0c;在b()中进…

手把手学浪视频怎么保存到本地

很多人在学浪app上面购买了课程,但是并不是所有课程都是永久观看,所以就想要下载下来,进行永久观看 由于很多人都是小白用户,考虑到这一点,我封装成软件,大家不需要考虑视频m3u8地址是怎么获取的、KEY是怎么解密的,只需要掌握工具怎么用 工具我也给大家准备好了,有需要的自己…

java算法day3

移除链表元素设计链表翻转链表两两交换链表中的结点 移除链表元素 ps&#xff1a;有时候感觉到底要不要写特判&#xff0c;你想到了就写&#xff01;因为一般特判有一劳永逸的作用。 解法有两种&#xff0c;一种是不用虚拟头结点&#xff0c;另一种就是用虚拟头结点。 这里我…