目录
- 前言
- SSE简介
- 优点
- 缺点
- Springboot集成SSE简约版
- Springboot集成SSE升级版
前言
通常在一些web项目中,会涉及到想客户端推送消息,常见的有Ajax轮询、webSocket,本篇文章主要使用Springboot集成SSE实现向客户端持续推送信息。
SSE简介
服务发送事件SSE(Sever-Sent Event),就是基于 HTTP 的技术,浏览器向服务器发送一个保持长连接HTTP请求,服务器单向地向客户端以流形式持续传输数据 。这样可以节约网络资源,不需要建立新连接。
优点
服务端不需要其他的类库,开发难度较低。
不用每次建立新连接,延迟较低。 数据通过简单且广泛使用的HTTP协议而不是专有协议进行同步。
对重新建立连接和事件ID功能的内置支持。
对于利用单向通信的应用程序和服务非常有用。
缺点
客户端越多连接越多,会占用服务器大量内存和连接数。
SSE只支持UTF-8编码,不支持二进制数据。
对最大打开连接数的严格限制可能使事情变得困难,每个浏览器都设置了限制。
SSE是单向的。
Springboot集成SSE简约版
客户端发送请求到服务端,服务端以流的形式不断向客户端推送数据示例,增加帅气值。
xml依赖:
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-web</artifactId> | |
</dependency> |
html代码:
<html> | |
<head> | |
<meta charset="UTF-"> | |
<title>Springboot集成SSE简约版</title> | |
<script type="text/javascript"> | |
let source = new EventSource('/get'); | |
source.onmessage = function (event) { | |
console.info(event.data); | |
document.getElementById('text').innerText = event.data | |
}; | |
</script> | |
</head> | |
<body> | |
<div id="text"></div> | |
</body> | |
</html> |
后端代码:
public void push(HttpServletResponse response) { | |
response.setContentType("text/event-stream"); | |
response.setCharacterEncoding("utf-"); | |
int i =; | |
while (true) { | |
try { | |
Thread.sleep(); | |
PrintWriter pw = response.getWriter(); | |
//注意返回数据必须以data:开头,"\n\n"结尾 | |
pw.write("data:xdm帅气值加" + i + "\n\n"); | |
pw.flush(); | |
//检测异常时断开连接 | |
if (pw.checkError()) { | |
log.error("客户端断开连接"); | |
return; | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
i++; | |
} | |
} |
效果:
Springboot集成SSE升级版
演示SSE的连接建立、接收数据和异常情况监听处理。
注:若浏览器不兼容在页面引入evensource.js。
<script src=/eventsource-polyfill.js></script>
客户端代码:
<html lang="en"> | |
<head> | |
<meta charset="UTF-"> | |
<title> Springboot集成SSE升级版</title> | |
</head> | |
<script> | |
let source = null; | |
const clientId = new Date().getTime(); | |
if (!!window.EventSource) { | |
source = new EventSource('/sse/create?clientId=' + clientId); | |
//建立连接 | |
source.onopen = function (event) { | |
setMessageInnerHTML("建立连接" + event); | |
} | |
//接收数据 | |
source.onmessage = function (event) { | |
setMessageInnerHTML(event.data); | |
} | |
//错误监听 | |
source.onerror = function (event) { | |
if (event.readyState === EventSource.CLOSED) { | |
setMessageInnerHTML("连接关闭"); | |
} else { | |
console.log(event); | |
} | |
} | |
} else { | |
setMessageInnerHTML("浏览器不支持SSE"); | |
} | |
window.onbeforeunload = function () { | |
close(); | |
}; | |
// 关闭 | |
function close() { | |
source.close(); | |
const httpRequest = new XMLHttpRequest(); | |
httpRequest.open('GET', '/sse/close/?clientId=' + clientId, true); | |
httpRequest.send(); | |
console.log("close"); | |
} | |
// 显示消息 | |
function setMessageInnerHTML(innerHTML) { | |
document.getElementById('text').innerHTML += innerHTML + '<br/>'; | |
} | |
</script> | |
<body> | |
<button onclick="close()">关闭连接</button> | |
<div id="text"></div> | |
</body> | |
</html> |
服务端代码:
private static Map<String, SseEmitter> cache = new ConcurrentHashMap<>(); | |
String clientId; | |
int sseId; | |
"/create") | (|
public SseEmitter create("clientId", required = false) String clientId) { (name = | |
// 设置超时时间,表示不过期。默认30000毫秒 | |
//可以在客户端一直断网、直接关闭页面但未提醒后端的情况下,服务端在一定时间等待后自动关闭网络连接 | |
SseEmitter sseEmitter = new SseEmitter(L); | |
// 是否需要给客户端推送ID | |
if (Strings.isBlank(clientId)) { | |
clientId = UUID.randomUUID().toString(); | |
} | |
this.clientId = clientId; | |
cache.put(clientId, sseEmitter); | |
log.info("sse连接,当前客户端:{}", clientId); | |
return sseEmitter; | |
} | |
"/3 * * * * ? ") | (cron =|
public void pushMessage() { | |
try { | |
sseId++; | |
SseEmitter sseEmitter = cache.get(clientId); | |
sseEmitter.send( | |
SseEmitter | |
.event() | |
.data("帅气值暴增" + sseId) | |
.id("" + sseId) | |
.reconnectTime() | |
); | |
} catch (Exception e) { | |
log.error(e.getMessage()); | |
sseId--; | |
} | |
} | |
"/close") | (|
public void close(String clientId) { | |
SseEmitter sseEmitter = cache.get(clientId); | |
if (sseEmitter != null) { | |
sseEmitter.complete(); | |
cache.remove(clientId); | |
} | |
} |
方法参数说明:
SseEmitter
.event()
.data("帅气值暴增" + sseId)
.id("" + sseId)
.reconnectTime(3000)
SseEmitter.event()
用来得到一个记录数据的容器。
.data("帅气值暴增" + sseId)
发送给客户端的数据。
.id("" + sseId)
记录发送数据的标识,服务端可以通过HttpServletRequest的请求头中拿到这个id,判断是否中间有误漏发数据。
.reconnectTime(3000)
定义在网络连接断开后,客户端向后端发起重连的时间间隔(以毫秒为单位)。
效果:
参考资料: