前言
软件开发定制通常在一些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>
- 1
- 2
- 3
- 4
html代码:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
后端代码:
@RequestMapping(value = "/get", produces = "text/event-stream;charset=UTF-8") public void push(HttpServletResponse response) { response.setContentType("text/event-stream"); response.setCharacterEncoding("utf-8"); int i = 0; while (true) { try { Thread.sleep(1000); PrintWriter pw = response.getWriter(); //注意返回数据必须以data:开头,"\"结尾 pw.write("data:xdm帅气值加" + i + "\"); pw.flush(); //检测异常时断开连接 if (pw.checkError()) { log.error("客户端断开连接"); return; } } catch (Exception e) { e.printStackTrace(); } i++; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果:
Springboot集成SSE升级版
演示SSE的连接建立、接收数据和异常情况监听处理。
注:若浏览器不兼容在页面引入。
<script src=/eventsource-polyfill.js></script>
- 1
客户端代码:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
服务端代码:
private static Map<String, SseEmitter> cache = new ConcurrentHashMap<>();String clientId;int sseId;@GetMapping("/create")public SseEmitter create(@RequestParam(name = "clientId", required = false) String clientId) { // 设置超时时间,0表示不过期。默认30000毫秒 //可以在客户端一直断网、直接关闭页面但未提醒后端的情况下,服务端在一定时间等待后自动关闭网络连接 SseEmitter sseEmitter = new SseEmitter(0L); // 是否需要给客户端推送ID if (Strings.isBlank(clientId)) { clientId = UUID.randomUUID().toString(); } this.clientId = clientId; cache.put(clientId, sseEmitter); log.info("sse连接,当前客户端:{}", clientId); return sseEmitter;}@Scheduled(cron = "0/3 * * * * ? ")public void pushMessage() { try { sseId++; SseEmitter sseEmitter = cache.get(clientId); sseEmitter.send( SseEmitter .event() .data("帅气值暴增" + sseId) .id("" + sseId) .reconnectTime(3000) ); } catch (Exception e) { log.error(e.getMessage()); sseId--; }}@GetMapping("/close")public void close(String clientId) { SseEmitter sseEmitter = cache.get(clientId); if (sseEmitter != null) { sseEmitter.complete(); cache.remove(clientId); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
方法参数说明:
SseEmitter .event() .data("帅气值暴增" + sseId) .id("" + sseId) .reconnectTime(3000)
- 1
- 2
- 3
- 4
- 5
SseEmitter.event()
用来得到一个记录数据的容器。
.data("帅气值暴增" + sseId)
发送给客户端的数据。
.id("" + sseId)
记录发送数据的标识,服务端可以通过HttpServletRequest的请求头中拿到这个id,判断是否中间有误漏发数据。
.reconnectTime(3000)
定义在网络连接断开后,客户端向后端发起重连的时间间隔(以毫秒为单位)。
效果:
参考资料: