前后端实时通讯方案
几种主流的实现方案:
轮询(前端主动去要)
前端间隔一定时间就调用后端提供的结果接口,比如 200 ms 一次,后端处理一些结果就累加放置在缓存中。 // 创建定时器,每隔200毫秒执行一次
let timer = setInterval(() => {
this.loadData();
},200)
SSE(后端推送给前端)
前端发送请求并和后端建立连接后,后端可以实时推送数据给前端,无需前端自主轮询。基本概念
服务器发送事件(Server-Sent Events)是一种用于从服务器到客户端的 **单向、实时** 数据传输技术,基于 HTTP 协议实现。它有几个重要的特点:
- **单向通信:** SSE 只支持服务器向客户端的单向通信,客户端不能向服务器发送数据。
- **文本格式:** SSE 使用 **纯文本格式 **传输数据,使用 HTTP 响应的 `text/event-stream`MIME 类型。
- **保持连接:** SSE 通过保持一个持久的 HTTP 连接,实现服务器向客户端推送更新,而不需要客户端频繁轮询。
- **自动重连:** 如果连接中断,浏览器会自动尝试重新连接,确保数据流的连续性。
SSE 数据格式
SSE 数据流的格式非常简单,每个事件使用 `data`字段,事件以两个换行符结束。还可以使用 `id`字段来标识事件,并且 `retry`字段可以设置重新连接的时间间隔。示例格式如下:
data:First message\n
\n
data:Second message\n
\n
data:Third message\n
id: 3\n
\n
retry: 10000\n
data:Fourth message\n
\n
自主实现 SSE
实现 SSE 非常简单,无论是 Java 服务端还是前端 HTML 5 都支持了 SSE,以下内容仅作了解。服务器端
需要生成符合 SSE 格式的响应,并设置合适的 HTTP 头。使用 Servlet 来实现 SSE 示例:import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/sse")
public class SseServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
for (int i = 0; i < 10; i++) {
writer.write("data: Message " + i + "\n\n");
writer.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
writer.close();
}
}
当然,如果是 Spring 项目,还有更简单的实现方式,直接给请求返回 SseEmitter 对象即可。
注意,一定要使用 Get 请求 ! ! !
示例代码:
@GetMapping("/sse")
public SseEmitter testSSE() {
// 建立 SSE 连接对象,0 表示不超时
SseEmitter emitter = new SseEmitter(0L);
...业务逻辑处理
return emitter;
}
- Web 前端
可以使用 JavaScript 的 EventSource对象来连接和处理服务器发送的事件。示例代码:
// 创建 SSE 请求
const eventSource = new EventSource("http://localhost:8080/sse");
// 接收消息
eventSource.onmessage = function (event) {
console.log(event.data);
};
// 生成结束,关闭连接
eventSource.onerror = function (event) {
if (event.eventPhase === EventSource.CLOSED) {
eventSource.close();
}
}
应用场景
由于现代浏览器普遍支持 SSE,所以它的应用场景非常广泛,AI 对话就是 SSE 的一个典型的应用场景。再举一些例子:
* 实时更新:股票价格、体育比赛比分、新闻更新等需要实时推送的应用。
* 日志监控:实时监控服务器日志或应用状态。
* 通知系统:向客户端推送系统通知或消息。
WebSocket
全双工协议,前端能实时推送数据给后端(或者从后端缓存拿数据),后端也可以实时推送数据给前端。介绍
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信 —— 浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的链接,并进行双向数据传输。
应用场景:
- 视频弹幕。
- 网页聊天。
- 体育实况更新。
- 股票基金报价实时更新。
入门案例
步骤:
- 需要页面作为 WebSocket 客户端。
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
</head>
<body>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
var clientId = Math.random().toString(36).substr(2);
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//连接WebSocket节点
websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(){
setMessageInnerHTML("连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
- 导入 WebSocket 的 maven 坐标。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 需要服务端组件 WebSocketServer,用于和客户端通信。
package com.sky.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket服务
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放会话对象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 需要配置类 WebSocketConfiguration,注册 WebSocket 的服务端组件。
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocket配置类,用于注册WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 需要定时任务类 WebSocketTask,定时向客户端推送数据。
package com.sky.task;
import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/**
* 通过WebSocket每隔5秒向客户端发送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}

浙公网安备 33010602011771号