springboot实现SSE(Server Sent Event)的示例代码

 更新时间:2024年04月22日 10:26:49   作者:爱码少年 00fly.online  
SSE 全称Server Sent Event,直译一下就是服务器发送事件,本文主要为大家详细介绍了springboot实现SSE的相关知识,需要的可以参考一下

一、概述

1.SSE是何方神圣

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

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

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

2.sse与websocket区别

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

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

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

二、实现过程

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

1.效果展示

http://124.71.129.204:8080/index

2. 简要流程

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="external nofollow"  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" rel="external nofollow"  target="_self">index</a></td>
				<td><a href="/404" rel="external nofollow"  target="_self">出错页面</a></td>
				<td><a href="/doc.html" rel="external nofollow"  target="_blank">doc.html</a></td>
				<td><a href="/h2-console" rel="external nofollow"  target="_blank">h2-console</a></td>
			</tr>
		</table>
		<div class="ex-page-content text-center">
			<h2 align="center">
				<a href="index" rel="external nofollow" >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

到此这篇关于springboot实现SSE(Server Sent Event)的示例代码的文章就介绍到这了,更多相关springboot实现SSE内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java代码实现银行管理系统

    java代码实现银行管理系统

    这篇文章主要为大家详细介绍了java代码实现银行管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • Java基于elasticsearch实现集群管理

    Java基于elasticsearch实现集群管理

    这篇文章主要介绍了java基于elasticsearch实现集群管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java FTPClient连接池的实现

    Java FTPClient连接池的实现

    这篇文章主要介绍了Java FTPClient连接池的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • Rocketmq事务消息之半消息详解

    Rocketmq事务消息之半消息详解

    这篇文章主要介绍了Rocketmq事务消息之半消息详解,RocketMQ的事务消息支持在业务逻辑与发送消息之间提供事务保证,RocketMQ通过两阶段的方式提供事务消息的支持,需要的朋友可以参考下
    2023-09-09
  • Java开发之内部类对象的创建及hook机制分析

    Java开发之内部类对象的创建及hook机制分析

    这篇文章主要介绍了Java开发之内部类对象的创建及hook机制,结合实例形式分析了java基于hook机制内部类对象的创建与使用,需要的朋友可以参考下
    2018-01-01
  • eclipse导入工程报错问题项目或者文件有红叉的解决方案

    eclipse导入工程报错问题项目或者文件有红叉的解决方案

    这篇文章主要介绍了eclipse导入工程报错问题项目或者文件有红叉的解决方案,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Java中HashMap和Hashtable的区别浅析

    Java中HashMap和Hashtable的区别浅析

    这篇文章主要介绍了Java中HashMap和Hashtable的区别浅析,本文总结了6条它们之间的不同之处,需要的朋友可以参考下
    2015-03-03
  • springboot集成swagger3与knife4j的详细代码

    springboot集成swagger3与knife4j的详细代码

    这篇文章主要介绍了springboot集成swagger3与knife4j,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • Java多线程中常见的锁策略详解

    Java多线程中常见的锁策略详解

    这篇文章主要介绍了Java多线程中常见的锁策略详解,在Java多线程中锁(synchronized)也会根据锁的竞争程度来升级为相关“高等级”锁,本文为了更好的理解 synchronized 加锁机制,对其做出了详细解释,需要的朋友可以参考下
    2023-07-07
  • 详解Java中的增强 for 循环 foreach

    详解Java中的增强 for 循环 foreach

    foreach 是 Java 中的一种语法糖,几乎每一种语言都有一些这样的语法糖来方便程序员进行开发,编译期间以特定的字节码或特定的方式来对这些语法进行处理。能够提高性能,并减少代码出错的几率。
    2017-05-05

最新评论