SSE(Server-Send-Event)服務端推送數據技術

SSE(Server-Send-Event)服務端推送數據技術

大家是否遇到過服務端需要主動傳輸數據到客戶端的情況,目前有三種解決方案。

  1. 客戶端輪詢更新數據。
  2. 服務端與客戶端建立 Socket 連接雙向通信
  3. 服務端與客戶建立 SSE 連接單向通信

幾種方案的比較:

  1. 輪詢:

    客戶端通過頻繁請求向服務端請求數據,達到類似實時更新的效果。輪詢的優點是實現簡單,但是會給服務端和網絡帶來額外的壓力,且延遲較高。

  2. WebSocket連接:

    服務端與客戶端建立Socket連接進行數據傳輸,Socket的傳輸方式是全雙工的。WebSocket是基于 TCP 的長連接,和HTTP 協議相比,它能實現輕量級的、低延遲的數據傳輸,非常適合實時通信場景,主要用于交互性強的雙向通信。

  3. SSE推送:

    SSE(Server-Sent Events)是一種基于 HTTP 協議的推送技術,只允許單向通訊。相較于 WebSocket,SSE 更簡單、更輕量級。

下面是SpringBoot使用SSE的步驟和示例代碼

  1. 配置依賴

    	    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
    

    SSE已經集成到spring-web中,所以可以直接使用。

  2. 后端代碼

    import com.wry.wry_test.service.SseService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import javax.validation.constraints.NotBlank;
    import java.util.concurrent.CompletableFuture;@RestController
    @RequestMapping("/sse")
    @Slf4j
    @Validated
    public class SseTestController {@Autowiredprivate SseService service;@GetMapping("/testSse")public SseEmitter testSse(@RequestParam("clientId") @NotBlank(message = "客戶端id不能為空") String clientId) {final SseEmitter emitter = service.getConn(clientId);CompletableFuture.runAsync(() -> {try {service.send(clientId);log.info("建立連接成功!clientId = {}", clientId);} catch (Exception e) {log.error("推送數據異常");}});return emitter;}@GetMapping("/sseConection")public SseEmitter createConnection(@RequestParam("clientId") @NotBlank(message = "客戶端id不能為空") String clientId) {return service.getConn(clientId);}@GetMapping("/sendMsg")public void sendMsg(@RequestParam("clientId") String clientId) {try {// 異步發送消息CompletableFuture.runAsync(() -> {try {service.send(clientId);} catch (Exception e) {log.error("推送數據異常");}});} catch (Exception e) {e.printStackTrace();}}@GetMapping("/sendMsgToAll")public void sendMsgToAll() {try {//異步發送消息CompletableFuture.runAsync(() -> {try {service.sendToAll();} catch (Exception e) {e.printStackTrace();}});} catch (Exception e) {e.printStackTrace();}}@GetMapping("closeConn/{clientId}")public String closeConn(@PathVariable("clientId") @NotBlank(message = "客戶端id不能為空") String clientId) {service.closeConn(clientId);return "連接已關閉";}}
    package com.wry.wry_test.service;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import javax.validation.constraints.NotBlank;public interface SseService {/*** 獲取連接* @param clientId 客戶端id* @return*/SseEmitter getConn(String clientId);/***  發送消息到指定客戶端* @param clientId 客戶端id* @throws Exception*/void send(String clientId);/*** 發送消息到所有SSE客戶端* @throws Exception*/void sendToAll() throws Exception;/*** 關閉指定客戶端的連接* @param clientId 客戶端id*/void closeConn(String clientId);
    }
    package com.wry.wry_test.service.impl;import com.wry.wry_test.service.SseService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import javax.validation.constraints.NotBlank;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;@Service
    @Slf4j
    public class SseServiceImpl implements SseService {private static final Map<String, SseEmitter> SSE_CACHE = new ConcurrentHashMap<>();@Overridepublic SseEmitter getConn(@NotBlank String clientId) {final SseEmitter sseEmitter = SSE_CACHE.get(clientId);if (sseEmitter != null) {return sseEmitter;} else {// 設置連接超時時間,需要配合配置項 spring.mvc.async.request-timeout: 600000 一起使用final SseEmitter emitter = new SseEmitter(600_000L);// 注冊超時回調,超時后觸發emitter.onTimeout(() -> {log.info("連接已超時,正準備關閉,clientId = {}", clientId);SSE_CACHE.remove(clientId);});// 注冊完成回調,調用 emitter.complete() 觸發emitter.onCompletion(() -> {log.info("連接已關閉,正準備釋放,clientId = {}", clientId);SSE_CACHE.remove(clientId);log.info("連接已釋放,clientId = {}", clientId);});// 注冊異常回調,調用 emitter.completeWithError() 觸發emitter.onError(throwable -> {log.error("連接已異常,正準備關閉,clientId = {}", clientId, throwable);SSE_CACHE.remove(clientId);});SSE_CACHE.put(clientId, emitter);log.info("建立連接成功!clientId = {}", clientId);return emitter;}}/*** 模擬類似于 chatGPT 的流式推送回答** @param clientId 客戶端 id* @throws IOException 異常*/@Overridepublic void send(@NotBlank String clientId) {final SseEmitter emitter = SSE_CACHE.get(clientId);if (emitter == null) return;// 開始推送數據// todo 模擬推送數據for (int i = 0; i < 10000000; i++) {String msg = "SSE 測試數據";try {this.sseSend(emitter, msg, clientId);Thread.sleep(1000);} catch (Exception e) {log.error("推送數據異常", e);break;}}log.info("推送數據結束,clientId = {}", clientId);// 結束推流emitter.complete();}/*** 發送數據給所有連接*/public void sendToAll() {List<SseEmitter> emitters = new ArrayList<>(SSE_CACHE.values());for (int i = 0; i < 10000000; i++) {String msg = "SSE 測試數據";this.sseSend(emitters, msg);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}@Overridepublic void closeConn(@NotBlank String clientId) {final SseEmitter sseEmitter = SSE_CACHE.get(clientId);if (sseEmitter != null) {sseEmitter.complete();}}/*** 推送數據封裝** @param emitter  sse長連接* @param data     發送數據* @param clientId 客戶端id*/private void sseSend(SseEmitter emitter, Object data, String clientId) {try {emitter.send(data);log.info("推送數據成功,clientId = {}", clientId);} catch (Exception e) {log.error("推送數據異常", e);throw new RuntimeException("推送數據異常");}}/*** 推送數據封裝** @param emitter sse長連接* @param data    發送數據*/private void sseSend(List<SseEmitter> emitter, Object data) {emitter.forEach(e -> {try {e.send(data);} catch (IOException ioException) {log.error("推送數據異常", ioException);}});log.info("推送數據成功");}}
    

    實現效果如下:服務端不斷推送數據到前端,前端可以也可以調用接口主動關閉連接。

    image-20240710180401231

適用場景:SSE由于是服務端單向通訊,所以適合那種需要單向持久的連接。比如:

  • ChatGPT這種實時加載會話數據
  • 文件下載,通過SSE異步下載文件
  • 服務端實時數據推送

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44115.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44115.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44115.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【前端】fis框架學習

文章目錄 1. 介紹 1. 介紹 FIS是專為解決前端開發中自動化工具、性能優化、模塊化框架、開發規范、代碼部署、開發流程等問題的工具框架。 使用FIS我們可以快速的完成各種前端項目的資源壓縮、合并等等各種性能優化工作&#xff0c;同時FIS還提供了大量的開發輔助功能 首先我們…

Nginx上配置多個網站

一、需求描述 我們只有一臺安裝了Nginx的服務器,但是我們需要實現在這臺服務器上部署多個網站,用以對外提供服務。 二、Nginx上配置多個網站分析 一般網站的格式為:【http://ip地址:端口號/URI】(比如:http://192.168.3.201:80),IP地址也可用域名表示;那么要實現在Nginx…

QT實現WebSocket通信

文章目錄 WebSocket服務端WebSocket客戶端html websocket客戶端在Qt5中實現WebSocket通信可以通過使用QtWebSockets模塊來實現。這個模塊提供了一個WebSocket客戶端和服務器的實現,可以很方便地在你的應用程序中集成WebSocket功能。 使用的時候,首先在pro工程文件中添加對應的…

【Vue】vue-element-admin概述

一、項目簡介 定位&#xff1a;vue-element-admin是一個后臺集成解決方案&#xff0c;旨在提供一種快速開發企業級后臺應用的方案&#xff0c;讓開發者能更專注于業務邏輯和功能實現&#xff0c;而非基礎架構的搭建。技術棧&#xff1a;該項目基于Vue.js、Element UI、Vue Rou…

Redis 7.x 系列【24】哨兵模式配置項

有道無術&#xff0c;術尚可求&#xff0c;有術無道&#xff0c;止于術。 本系列Redis 版本 7.2.5 源碼地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目錄 1. 前言2. 配置項2.1 protected-mode2.2 port2.3 daemonize2.4 pidfile2.5 loglevel2.…

i18n、L10n、G11N 和 T9N 的含義

注&#xff1a;機翻&#xff0c;未校對。 Looking into localization for the first time can be terrifying, if only due to all of the abbreviations. But the meaning of i18n, L10n, G11N, and T9N, are all very easy to understand. 第一次研究本地化可能會很可怕&…

深入探索Python Web抓取世界:利用BeautifulSoup與Pandas構建全面的網頁數據采集與分析流程

引言 在信息爆炸的時代&#xff0c;網絡成為了一個無盡的知識寶庫&#xff0c;其中包含了大量有價值的公開數據。Python作為一種靈活多變且具有強大生態系統支持的編程語言&#xff0c;尤其擅長于數據的收集、處理與分析工作。本文將聚焦于Python的兩大利器——BeautifulSoup和…

如何做一個遲鈍不受傷的打工人?

一、背景 在當前激烈的職場環境中&#xff0c;想要成為一個相對“遲鈍”且不易受傷的打工人&#xff0c;以下是一些建議&#xff0c;但請注意&#xff0c;這里的“遲鈍”并非指智力上的遲鈍&#xff0c;而是指在應對復雜人際關系和壓力時展現出的豁達與鈍感力&#xff1a; 尊重…

【測開能力提升-fastapi框架】fastapi路由分發

1.7 路由分發 apps/app01.py from fastapi import APIRouterapp01 APIRouter()app01.get("/food") async def shop_food():return {"shop": "food"}app01.get("/bed") async def shop_food():return {"shop": "bed&…

部署stable-diffusion時遇到RuntimeError: Couldn‘t clone Stable Diffusion XL.問題

錯誤信息如下&#xff1a; venv "E:\AI\stable-diffusion-webui-master\venv\Scripts\Python.exe" fatal: ambiguous argument HEAD: unknown revision or path not in the working tree. Use -- to separate paths from revisions, like this: git <command>…

js前端隱藏列 并且獲取值,列表復選框

列表框 <div class"block" id"psi_wh_allocation_m"><table id"result" class"list auto hover fixed" style"width:100%;border-collapse:collapse"><thead><tr><%--<th></th>--%&…

LabVIEW濾波器性能研究

為了研究濾波器的濾波性能&#xff0c;采用LabVIEW設計了一套濾波器性能研究系統。該系統通過LabVIEW中的波形生成函數&#xff0c;輸出幅值及頻率可調的正弦波和白噪聲兩種信號&#xff0c;并將白噪聲與正弦波疊加&#xff0c;再通過濾波器輸出純凈的正弦波信號。系統通過FFT&…

Python從0到100(三十八):json字符串的數據提取

JSON的數據提取 1.學習目標 掌握JSON相關的方法&#xff08;load, loads, dump, dumps&#xff09;了解JSONPath的使用&#xff08;提取JSON中的數據&#xff09; 2 復習什么是JSON JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式&#xff0c;它使得人們很容…

富文本braft-editor插件分享

效果展示 安裝插件 npm install braft-editor 或者 yarn add braft-editor 主要代碼 import React, { useState, forwardRef } from react //引入富文本編輯器 import BraftEditor from braft-editor // 引入編輯器樣式 import braft-editor/dist/index.css import { B…

thinkphp8框架源碼精講

前言 很開心你能看到這個筆記&#xff0c;相信你對thinkphp是有一定興趣的&#xff0c;正好大家都是志同道合的人。 thinkphp是我入門學習的第一個框架&#xff0c;經過這么多年了&#xff0c;還沒好好的研究它&#xff0c;今年利用了空閑的時間狠狠的深入源碼學習了一把&…

缺陷檢測總結

基于深度學習的缺陷檢測方法 1、全監督模型&#xff1a;基于表征學習的缺陷檢測模型&#xff0c;基于度量學習的缺陷檢測模型 1.1、基于表征學習的缺陷檢測模型&#xff1a;分類網絡&#xff0c;檢測網絡&#xff0c;分割網絡&#xff1b; 其中分類網絡的使用方式主要有三種…

2974. 最小數字游戲 Easy

你有一個下標從 0 開始、長度為 偶數 的整數數組 nums &#xff0c;同時還有一個空數組 arr 。Alice 和 Bob 決定玩一個游戲&#xff0c;游戲中每一輪 Alice 和 Bob 都會各自執行一次操作。游戲規則如下&#xff1a; 每一輪&#xff0c;Alice 先從 nums 中移除一個 最小 元素&a…

硅谷甄選運營平臺-vue3組件通信方式

vue3組件通信方式 vue2組件通信方式&#xff1a; props:可以實現父子組件、子父組件、甚至兄弟組件通信自定義事件:可以實現子父組件通信全局事件總線$bus:可以實現任意組件通信pubsub:發布訂閱模式實現任意組件通信vuex:集中式狀態管理容器&#xff0c;實現任意組件通信ref:父…

camunda最終章-springboot

1.實現并行流子流程 1.畫圖 2.創建實體 package com.jmj.camunda7test.subProcess.entity;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.io.Serializable; import java.util.ArrayList; import java.util.List;Data …

C語言 | Leetcode C語言題解之第230題二叉搜索樹中第K小的元素

題目&#xff1a; 題解&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int search_num(struct TreeNode* root, int k, int *result, int num) {if(num k 1){retu…