前后端实时通讯方案

几种主流的实现方案:

轮询(前端主动去要)

前端间隔一定时间就调用后端提供的结果接口,比如 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;
}
  1. 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 的一种新的网络协议。它实现了浏览器与服务器全双工通信 —— 浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的链接,并进行双向数据传输。

应用场景:

  1. 视频弹幕。
  2. 网页聊天。
  3. 体育实况更新。
  4. 股票基金报价实时更新。

入门案例

步骤:

  1. 需要页面作为 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>

  1. 导入 WebSocket 的 maven 坐标。
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 需要服务端组件 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();
            }
        }
    }

}
  1. 需要配置类 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();
    }

}
  1. 需要定时任务类 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()));
    }
}
posted @ 2025-07-03 00:30  是小陈呀  阅读(95)  评论(0)    收藏  举报