实时数据推送并非只有WebSocket一种选择

环境:SpringBoot2.7.16


概述

在Web应用中,有几种实时数据推送的选择方案,包括SSE(Server-Sent Events)、WebSocket、长轮询等。

SSE是一种基于HTTP协议的服务器向客户端推送数据的技术。它的优点是实现简单、轻量级,对现有服务器软件兼容性好。但是,由于SSE是单向通信模型,只能由服务器向客户端推送数据,对于需要客户端向服务器发送数据的场景,SSE就无法满足需求。

WebSocket是一种双向通信模型,允许客户端和服务器之间互相发送消息。它的优点是实时性强、延迟低,但是需要服务器端支持对应的协议栈,实现起来相对复杂一些。

长轮询是对短轮询的一种改进版本,通过在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。长轮询在客户端发起请求时,服务器会保持连接打开,等待一定时间后再返回响应。这样可以减少客户端频繁的请求,节省带宽和服务器资源。但是,如果服务器没有新的消息产生,客户端会一直等待响应,实时性就会受到一定影响。

根据实际应用场景和需求,可以选择适合的实时数据推送方案。如果只需要服务器向客户端推送数据,且对实时性要求不是特别高,可以选择SSE。如果需要客户端向服务器发送数据,或者对实时性要求较高,可以选择WebSocket或长轮询。当然,也可以根据实际情况将这几种方案结合起来使用,以满足不同的需求。

SSE与WebSocket对比

SSE(Server-Sent Events)和WebSocket都是用于实现实时通信的技术,存在关键差异。

通信模型:SSE是单向通信模型,只能由服务器向客户端推送数据。而WebSocket是双向通信模型,客户端和服务器可以互相发送消息。

连接性:SSE使用长轮询或HTTP流技术,需要频繁地发起HTTP请求来获取数据。而 WebSocket只需在握手阶段建立一次连接,然后保持连接打开,减少了频繁建立连接的开销。

实时性:WebSocket提供了更低的延迟和更高的实时性,因为它支持双向通信,可以立即将数据推送给客户端。SSE虽然也可以实现实时性,但由于其单向通信模型,需要服务器定期发送数据。

协议特性:SSE是部署在HTTP协议之上的,现有的服务器软件都支持。而WebSocket是一个新的协议,需要服务器端支持对应的协议栈。

复杂性:SSE相对WebSocket来说更轻量级,实现更简单。WebSocket协议较复杂,实现相对困难一些。

总体来说,SSE和WebSocket都有各自的优点和适用场景。SSE轻量级且对现有服务器软件兼容性好,而WebSocket则提供了更强的双向通信能力和更高的实时性。

SSE简介

SSE(Server-Sent Events)是一种用于实现服务器向客户端实时推送数据的Web技术。与传统的轮询和长轮询相比,SSE提供了更高效和实时的数据推送机制。

SSE基于HTTP协议,允许服务器将数据以事件流(Event Stream)的形式发送给客户端。客户端通过建立持久的HTTP连接,并监听事件流,可以实时接收服务器推送的数据。

SSE的主要特点包括:

简单易用:SSE使用基于文本的数据格式,如纯文本、JSON等,使得数据的发送和解析都相对简单。

单向通信:SSE支持服务器向客户端的单向通信,服务器可以主动推送数据给客户端。

实时性:SSE建立长时间的连接,使得服务器可以实时地将数据推送给客户端,而无需客户端频繁地发起请求。

服务端开发

依赖管理

XML 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

配置文件

XML 复制代码
spring:
  mvc:
    static-path-pattern: /**
  web:
    resources:
      #静态文件目录index.html
      static-locations: classpath:/templates/

接口开发

java 复制代码
@RestController
@RequestMapping("/sse")
public class SseController {
  
  // 该集合用来管理所有客户端的连接
  private final Map<String, SseEmitter> sse = new ConcurrentHashMap<>() ;

  // 创建连接接口,同时指定了消息类型为text/event-stream
  @GetMapping(path="/events/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
  public SseEmitter createConnect(@PathVariable("id") String id) throws IOException {
    SseEmitter emitter = new SseEmitter(0L);
    // 每一个客户端保存到Map中
    sse.put(id, emitter) ;
    // 当发生错误的回调
    emitter.onError(ex -> {
      System.err.printf("userId: %s, error: %s%n", id, ex.getMessage()) ;
      sse.remove(id) ;
    }) ;
   // 异步请求完成后的回调
    emitter.onCompletion(() -> {
      sse.remove(id) ;
      System.out.printf("%s, 请求完成...") ;
    }) ;
    // 异步请求超时回调
    emitter.onTimeout(() -> {
      System.err.println("超时...") ;
    }) ;
    return emitter;
  }
  
  // 该接口用来进行消息的发送
  // 由客户端发起请求,然后根据id获取相应的SseEmitter进行消息的发送
  @GetMapping("/sender/{id}")
  public String sender(@PathVariable("id") String id) throws Exception {
    SseEmitter emitter = this.sse.get(id) ;
    if (emitter != null) {
      try {
        emitter.send( "随机消息 - " + new Random().nextInt(10000000)) ;
      } catch (Exception e) {
        System.err.println("%s%n", e.getMessage()) ;
      }
    }
    return "success" ;
  }
}

前端开发

前端比较简单就是一个index.html页面​​​​​​​

html 复制代码
<html>
  <head>
  <title>SSE</title>
</head>
<body>
  <button type="button" onclick="closeSse()">Close</button>
  <hr style="margin: 2px; padding: 0px 0px;"/>
  <ul id="list"></ul>
</body>
<script>
  const evtSource = new EventSource(`/sse/events/${Date.now()}`) ;
  evtSource.onmessage = (event) => {
    const newElement = document.createElement("li") ;
    const eventList = document.getElementById("list") ;
    newElement.innerHTML = "接收到消息: " + event.data ;
    eventList.appendChild(newElement) ;
  };
  evtSource.onopen = (event) => {
    console.log('建立连接...')
  };
  evtSource.onerror = (event) => {
    console.error("发生错误:", event) ;
  };
  function closeSse() {
    evtSource.close() ;
  }
</script>
</html>

以上就是前后端的开发,代码非常的简单;也就简单的实现了由服务端实时数据推送。

EventSource对象的readyState有3个状态值:

0 --- connecting

1 --- open

2 --- closed

测试

调用消息发送接口

自定义事件类型

修改消息发送接口​​​​​​​

java 复制代码
@GetMapping("/sender/{id}")
  public String sender(@PathVariable("id") String id) throws Exception {
    SseEmitter emitter = this.sse.get(id) ;
    if (emitter != null) {
      SseEventBuilder builder = SseEmitter.event() ;
      // 指定事件类型
      builder.name("chat") ;
      String msg = "随机消息 - " + new Random().nextInt(10000000);
      builder.data(msg) ;
      try {
        emitter.send(builder) ;
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return "success" ;
  }

前端监听具体事件类型消息​​​​​​​

javascript 复制代码
evtSource.addEventListener("chat", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");

  newElement.innerHTML = "chat message: " + event.data;
  eventList.appendChild(newElement);
});

注意:默认是"message"事件,因为它可以捕获没有 event 字段的事件, * 以及具有特定类型 `event:message` 的事件。* 它不会触发任何其他类型的事件。

完毕!!!

关注+转发

关注公众号,全都是实战案例

相关推荐
皮皮林5519 小时前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
Sheffield13 小时前
Docker的跨主机服务与其对应的优缺点
linux·网络协议·docker
用户908324602733 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840824 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解4 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解4 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记4 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者5 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840825 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解5 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端