说明
使用Java开发web应用,大多数时候我们提供的接口返回数据都是一次性完整返回。有些时候,我们也需要提供流式接口持续写出数据,以下提供一种简单的方式。
SSE(Server-Sent Events)
SSE 是一种允许服务器单向发送事件到客户端的技术,它基于HTTP协议,服务器可以推送消息到客户端,但客户端不能向服务器发送消息。
SSE与WebSocket的比较
通信方式:SSE是单向的,只能由服务器向客户端发送数据;而WebSocket是双向的,服务器和客户端都可以发送数据。
协议:SSE基于HTTP协议,更易于设置和配置;WebSocket是一个独立的协议。
数据格式:SSE发送的数据格式固定,必须是"text/event-stream";而WebSocket可以发送任何类型的数据。
连接:SSE在断开连接后可以自动重新连接,而WebSocket需要手动处理重连。
浏览器支持:WebSocket的浏览器支持更广泛,几乎所有现代浏览器都支持WebSocket;而SSE在某些旧版本的浏览器(如IE)中不被支持。
SSE与长轮询的比较
效率:SSE更高效,因为它只需要一个HTTP连接,就可以持续地发送数据;而长轮询需要不断地建立和断开HTTP连接。
实时性:SSE的实时性更强,因为服务器可以随时发送数据;而长轮询需要客户端不断地发送请求来获取新数据。
复杂性:SSE的实现相对简单,只需要服务器按照规定的格式发送数据即可;而长轮询的实现较复杂,需要处理连接的建立和断开,以及错误和超时等问题。
浏览器支持:与WebSocket相比,SSE和长轮询的浏览器支持都较差,但长轮询在更多的浏览器中被支持。
适用场景:SSE适用于服务器需要主动推送数据的场景;而长轮询适用于客户端需要定期获取新数据,但服务器不需要主动推送数据的场景。
直接干代码
引入pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
后端JAVA代码
package com.wyd.controller;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
@RestController
public class SseController {
@GetMapping("/sse")
public void sse(HttpServletResponse response) {
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
try (final PrintWriter writer = response.getWriter()) {
// 要推送的内容
final String content = "你好,我的朋友,快过年了,提前祝你新年快乐!";
int len = content.length();
int endIndex = 0;
// 每隔2个字符推送一次,模拟打字机效果
while (endIndex < len) {
endIndex = Math.min(endIndex + 2, len);
final String subContent = content.substring(0, endIndex);
// 将要推送的内容封装成JSON格式,模拟实际开发中的数据格式,非必须
final JSONObject json = new JSONObject();
json.put("data", subContent);
json.put("code", HttpStatus.OK.value());
// 最后一次推送时,type为finish,表示推送结束,其它情况为add
final String type = endIndex == len
? "finish"
: "add";
json.put("type", type);
// 组装成SSE格式的数据,发送给前端,这个格式(data: content\n\n)是固定的,content是自定义的推送内容
writer.write("data: " + json.toJSONString() + "\n\n");
writer.flush();
// 稍微给点停顿,防止数据发送太快,浏览器接收不过来
TimeUnit.MILLISECONDS.sleep(1000);
}
} catch (Exception e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE Example</title>
</head>
<body>
<h1>服务器推送事件 (SSE)</h1>
<div id="events"></div>
<script>
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
const newElement = document.createElement("div");
newElement.textContent = event.data;
document.getElementById("events").appendChild(newElement);
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
eventSource.close();
};
</script>
</body>
</html>
效果图