国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Server side event (SSE)實(shí)現(xiàn)消息推送功能

這篇具有很好參考價(jià)值的文章主要介紹了Server side event (SSE)實(shí)現(xiàn)消息推送功能。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

問題場景

?在開發(fā)web項(xiàng)目時(shí),有一個(gè)需求是:后端服務(wù)器主動(dòng)地、不斷地推送消息給客戶端網(wǎng)頁。要實(shí)現(xiàn)該需求,需要先考慮幾個(gè)常用的技術(shù)方案:

  • 在客戶端網(wǎng)頁用fetchXmlHttpRequest發(fā)送請(qǐng)求是行不通的,因?yàn)檫@類請(qǐng)求在后端返回一次數(shù)據(jù)之后就會(huì)中斷連接,導(dǎo)致后端無法主動(dòng)地傳數(shù)據(jù)給客戶端。
  • 客戶端網(wǎng)頁使用輪詢或者長輪詢資源效率低,會(huì)花很多時(shí)間在不斷地建立網(wǎng)絡(luò)連接、不斷地?cái)嚅_網(wǎng)絡(luò)連接。(不熟悉輪詢和長輪詢的朋友可以自己百度一下,有很多博文)。
  • 客戶端和后端一同使用WebSocket的話,雖然只需要建立一次網(wǎng)絡(luò)連接就能讓客戶端和后端都主動(dòng)地、不斷地向?qū)Ψ桨l(fā)送消息,但該項(xiàng)目只需要后端向客戶端主動(dòng)地、不斷地發(fā)送消息,且WebSocket使用起來相對(duì)復(fù)雜,因此沒必要用它。
  • 客戶端和后端一同使用Server side event (SSE)的話,能在建立一次網(wǎng)絡(luò)連接后,讓后端向客戶端主動(dòng)地、不斷地發(fā)送消息,并且使用起來也非常簡單,是實(shí)現(xiàn)項(xiàng)目功能的首選方案。

Server side event (SSE)簡介

?SSE定義了客戶端網(wǎng)頁和后端服務(wù)器的網(wǎng)絡(luò)連接方式,具體包括:

  1. 先由客戶端發(fā)送一次http請(qǐng)求給后端(注意,第一次http請(qǐng)求的發(fā)起者永遠(yuǎn)是客戶端而不是后端)。
  2. 后端接收請(qǐng)求并建立網(wǎng)絡(luò)連接,該網(wǎng)絡(luò)連接將長久存在,可以一直被使用,直到下述第4點(diǎn)發(fā)生。
  3. 在網(wǎng)絡(luò)連接后,后端不會(huì)立馬發(fā)送數(shù)據(jù)給客戶端,而是在后端運(yùn)行到了自定義的條件時(shí)(如數(shù)據(jù)庫里更新了數(shù)據(jù)),再將數(shù)據(jù)通過網(wǎng)絡(luò)連接發(fā)送給客戶端。接著后端會(huì)繼續(xù)等待這個(gè)自定義的條件的發(fā)生。因此,后端能夠主動(dòng)地、不斷地向客戶端發(fā)送數(shù)據(jù)。
  4. 有4種讓網(wǎng)絡(luò)連接斷開的情況:
    ①網(wǎng)絡(luò)問題(如客戶端斷網(wǎng)),瀏覽器監(jiān)測(cè)到網(wǎng)絡(luò)問題后,每隔一段時(shí)間就會(huì)向后端發(fā)起重連接請(qǐng)求;后端是察覺不到客戶端已經(jīng)斷網(wǎng)了的,只有在后端向客戶端發(fā)送數(shù)據(jù),但收到了發(fā)送錯(cuò)誤的信息后,才會(huì)在后端也斷開連接。
    ②網(wǎng)絡(luò)連接的時(shí)長超出了后端設(shè)置的超時(shí)時(shí)間,此時(shí)后端會(huì)斷開網(wǎng)絡(luò)連接,并且通知客戶端也要斷開,但這之后客戶端仍然會(huì)不斷地發(fā)起重連接請(qǐng)求,以便繼續(xù)傳輸數(shù)據(jù)。
    ③后端主動(dòng)地調(diào)用SSE的斷開函數(shù),此時(shí)客戶端和后端的網(wǎng)絡(luò)連接就會(huì)斷開,客戶端仍然會(huì)不斷重連。
    ④客戶端主動(dòng)地調(diào)用SSE的斷開函數(shù),此時(shí)客戶端和后端的網(wǎng)絡(luò)連接就會(huì)斷開,但是客戶端不會(huì)再重連。
    一旦客戶端重連成功,情況又變到了上面的第2點(diǎn)的位置。

在后端使用SSE

?個(gè)人使用的后端框架是Springboot,該框架自帶實(shí)現(xiàn)了SSE的類,我們只需要如下4步即可讓后端能實(shí)現(xiàn)SSE功能。

  1. 在項(xiàng)目的pom.xml文件中導(dǎo)入Springboot的web依賴,從而導(dǎo)入實(shí)現(xiàn)了SSE功能的類。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 在項(xiàng)目的Controller類里定義返回SseEmitter對(duì)象的函數(shù),來讓Springboot自動(dòng)建立客戶端和后端的SSE連接。
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
SseEmitter emitter; // 聲明SseEmitter類型的變量
@GetMapping("/test/sse")
@ResponseBody // @ResponseBody讓函數(shù)返回的對(duì)象不被解析成前端文件的路徑
public SseEmitter handle(){
    emitter=new SseEmitter();
    return emitter;
}

只要客戶端訪問了/test/sse這個(gè)路徑,Springboot會(huì)根據(jù)返回的SseEmitter對(duì)象,來自動(dòng)建立起客戶端和后端的SSE連接,不需要我們?cè)賮碜鼋⒕W(wǎng)絡(luò)連接的事。

  1. 在項(xiàng)目的Controller類里定義讓SseEmitter發(fā)送數(shù)據(jù)的函數(shù)。
public void send() throws IOException{
      emitter.send(
          SseEmitter
          .event()
          .data("<Your Data>")
          .id("<Your Id>")
          .reconnectTime(<Your Time>)
      );

其中SseEmitter.event().data("<Your Data>").id("<Your Id>").reconnectTime()得到的類型是SseEventBuilder類,用來定義要發(fā)送的數(shù)據(jù)。然后調(diào)用SseEmittersend(SseEventBuilder)方法,SseEmitter便會(huì)自動(dòng)地把定義好的數(shù)據(jù)發(fā)送給客戶端。說一句,SseEmitter發(fā)送數(shù)據(jù)不一定要靠send(SseEventBuilder)方法,只是個(gè)人覺得這樣比較方便且功能完備,至于SseEmitter還有哪些發(fā)送數(shù)據(jù)的方法,直接看官方的SseEmitter文檔吧。


?接下來說一下SSE發(fā)送的數(shù)據(jù)包含哪些東西。就拿上述SseEmitter.event().data("<Your Data>").id("<Your Id>").reconnectTime()定義的數(shù)據(jù)來說:

  1. SseEmitter.event()用來得到一個(gè)記錄數(shù)據(jù)的容器(該容器使用建造者模式添加數(shù)據(jù)),該方法不帶任何參數(shù)。
  2. .data("<Your Data>")"<Your Data>"來添加傳輸給客戶端的數(shù)據(jù),參數(shù)是Object類型,但最好以字符串為參數(shù),這樣就不需要擔(dān)心SseEmitter會(huì)怎樣處理Object了(如無需擔(dān)心SseEmitter怎么自動(dòng)處理Map)。如果是想返回JSON數(shù)據(jù),可先將JSON數(shù)據(jù)轉(zhuǎn)換為字符串;如果是想返回二進(jìn)制數(shù)據(jù),可以用base64的編碼方式,每6個(gè)比特用一個(gè)字符來代替,再由客戶端將字符解碼為比特。
  3. .id("<Your Id>")"<Your Id>"來作為這條數(shù)據(jù)的id。每當(dāng)客戶端要重連時(shí)(由于斷網(wǎng)、或者網(wǎng)絡(luò)連接時(shí)長達(dá)到了后端設(shè)置的超時(shí)時(shí)間等),客戶端就會(huì)將最后收到的那條數(shù)據(jù)的id,連同重連接請(qǐng)求一并發(fā)給后端,從而讓后端知道客戶端已經(jīng)接收了哪些數(shù)據(jù)。后端可以在HttpServletRequest的請(qǐng)求頭中拿到這個(gè)id,至于拿到id之后怎么處理,就需要后端自定義函數(shù)了。舉兩個(gè)例子,一個(gè)例子是后端拿到id后不做任何處理的,另一個(gè)例子是后端拿到id后,對(duì)比自己已產(chǎn)生的id,來推斷出客戶端漏接收了哪些數(shù)據(jù),然后將客戶端漏收的數(shù)據(jù)補(bǔ)發(fā)出去。
// 例子1:后端拿到id后不做任何處理
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

SseEmitter emitter; // 聲明SseEmitter類型的變量
@GetMapping("/test/sse")
@ResponseBody
// 客戶端每次重連,都是在觸發(fā)該函數(shù),因此在該函數(shù)用HttpServletRequest即可獲取請(qǐng)求里的id
public SseEmitter handle(HttpServletRequest request){
	String lastId=request.getHeader("Last-Event-ID");
	// 雖然獲得了該id,但不做任何事情,客戶端有沒有漏收數(shù)據(jù)就不管了 
    emitter=new SseEmitter();
    return emitter;
}

// 例子2:后端拿到id后,看客戶端漏收了哪些數(shù)據(jù),漏收的要補(bǔ)發(fā)

SseEmitter emitter; // 聲明SseEmitter類型的變量
Integer backEndId; // 記錄后端最新數(shù)據(jù)的id
@GetMapping("/test/sse")
@ResponseBody
// 客戶端每次重連,都是在觸發(fā)該函數(shù),因此在該函數(shù)用HttpServletRequest即可獲取請(qǐng)求里的id
public SseEmitter handle(HttpServletRequest request){
	String lastId=request.getHeader("Last-Event-ID");
	// 獲得了客戶端最后收到的數(shù)據(jù)的id后,對(duì)比后端最新數(shù)據(jù)的id,來補(bǔ)發(fā)客戶端漏收的數(shù)據(jù)
	// 自定義一個(gè)函數(shù)來決定要補(bǔ)發(fā)的數(shù)據(jù),如定義下面的compare()函數(shù)
	String data=compare(lastId,backEndId) 
    emitter=new SseEmitter();
    // 先添加要發(fā)送的數(shù)據(jù)給SseEmitter ,等Springboot自動(dòng)建立SSE連接后便會(huì)自動(dòng)發(fā)送該數(shù)據(jù)
    emitter.send(
          SseEmitter
          .event()
          .data(data)
          .id("<Your Id>")
          .reconnectTime(5000)
      );
    return emitter;
}
  • .reconnectTime(<Your Time>)<Your Time>定義在網(wǎng)絡(luò)連接斷開后,客戶端向后端發(fā)起重連的時(shí)間間隔(以毫秒為單位),網(wǎng)絡(luò)連接斷開并重連的情況見開頭那幾點(diǎn)。這個(gè)東西的作用就是讓客戶端在網(wǎng)絡(luò)連接斷開的情況下重連后端,恢復(fù)數(shù)據(jù)傳輸。

    既然說到了客戶端的時(shí)間設(shè)置.reconnectTime(<Your Time>),那么另外一個(gè)和時(shí)間相關(guān)的設(shè)置就是后端SseEmitter對(duì)象的超時(shí)時(shí)間。在用new SseEmitter()實(shí)例化SseEmitter 對(duì)象時(shí),還可以輸入一個(gè)long型參數(shù)(如new SseEmitter(timeout); ),代表后端SseEmitter 對(duì)象的超時(shí)時(shí)間(以毫秒為單位),不傳入?yún)?shù)的話則默認(rèn)是30000毫秒。如果SseEmitter 對(duì)象的存在時(shí)間超過了設(shè)定的超時(shí)時(shí)間,那么后端和客戶端分別發(fā)生以下事情:

    • 后端的SseEmitter 對(duì)象會(huì)主動(dòng)通知客戶端已超時(shí),并且這個(gè)SseEmitter 對(duì)象已無法再發(fā)送新數(shù)據(jù)(此時(shí)用send()方法發(fā)送會(huì)拋異常)。
    • 客戶端方面,如果后端給客戶端發(fā)送過數(shù)據(jù),那么客戶端接收到超時(shí)通知后會(huì)一直自動(dòng)重連(也就是重新訪問"/test/sse"路徑),重連的時(shí)間間隔為.reconnectTime(<Your Time>)中設(shè)定的時(shí)間,在得到一個(gè)新的SseEmitter對(duì)象后繼續(xù)發(fā)送數(shù)據(jù)。但如果后端從未用SseEmitter對(duì)象發(fā)送過任何數(shù)據(jù),那么客戶端便不會(huì)自動(dòng)重連,而是直接報(bào)503的http錯(cuò)誤代碼。
    • 說一下超時(shí)時(shí)間的作用,后端設(shè)置超時(shí)時(shí)間,可以在客戶端一直斷網(wǎng)、直接關(guān)閉頁面但未提醒后端的情況下,讓后端在一定時(shí)間等待后自動(dòng)關(guān)閉網(wǎng)絡(luò)連接,節(jié)省資源。如果不存在超時(shí)時(shí)間的話,一旦遇到以上意外情況,后端就會(huì)一直維持該網(wǎng)絡(luò)連接,直到某一次發(fā)送數(shù)據(jù)發(fā)現(xiàn)連接不上才會(huì)去斷開后端的連接,這會(huì)浪費(fèi)資源。

客戶端使用SSE


?客戶端和后端建立SSE連接,需要雙方都做出努力,不是某一方做了另一方就不用做。基于Springboot的后端如何建立SSE連接已經(jīng)在上面講了,這部分就講下客戶端的Javascript該如何建立SSE連接。
?客戶端需要做3件事:

  1. 實(shí)例化支持SSE功能的對(duì)象,在絕大部分瀏覽器都內(nèi)置了EventSource對(duì)象,可以用于實(shí)現(xiàn)SSE功能。(好用的Chrome, Firefox, Edge當(dāng)然內(nèi)置了該對(duì)象;但I(xiàn)E瀏覽器沒內(nèi)置該對(duì)象,不過這對(duì)IE瀏覽器來說還挺合理)
// new一個(gè)EventSource對(duì)象,第一個(gè)參數(shù)是后端的訪問地址;第二個(gè)參數(shù)是可選的,如果要填就只能填{withCredentials:true}或{withCredentials:true},表示發(fā)送或不發(fā)送Cookie
var eventSource = new EventSource("http://127.0.0.1:8000/test/sse");

這一行就可以讓客戶端向后端發(fā)送建立SSE連接的請(qǐng)求,只要后端成功建立了SSE連接,后端就可以主動(dòng)向客戶端主動(dòng)、不斷地發(fā)送數(shù)據(jù)了。

  1. 監(jiān)聽 EventSource對(duì)象接收數(shù)據(jù)、報(bào)錯(cuò)的事件。
// 監(jiān)聽接受數(shù)據(jù)的事件
eventSource.addEventListener("message", function(event) {console.log(event.data)});

EventSource默認(rèn)支持三類事件:“open”, “message”, “error”;分別表示客戶端和后端建立了連接、客戶端接收到了來自后端的數(shù)據(jù)、客戶端報(bào)錯(cuò)這三個(gè)場景。使用addEventListener()用于注冊(cè)事件,第一個(gè)參數(shù)是事件名,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)自帶一個(gè)MessageEvent 對(duì)象,MessageEvent.data 表示后端傳來的數(shù)據(jù),MessageEvent.lastEventId能表示后端傳來的數(shù)據(jù)id。客戶端拿到MessageEvent.data 后,就可以用來更新用戶的頁面了,不一定非要像console.log(event.data)在控制臺(tái)打印。

  1. 在任務(wù)結(jié)束后關(guān)閉SSE連接。下面這行會(huì)讓客戶端自己關(guān)閉SSE連接,并且也會(huì)通知后端關(guān)閉SSE連接。
eventSource.close()

實(shí)例

為了展示客戶端和后端的SSE通信,實(shí)現(xiàn)這樣一個(gè)簡單的例子:

  1. 客戶端用EventSource發(fā)起創(chuàng)建SSE連接的請(qǐng)求給后端,后端進(jìn)行接收并建立起SSE連接。

  2. 后端每隔5秒向客戶端發(fā)送一次數(shù)據(jù),并設(shè)置id用于展示如何補(bǔ)發(fā)數(shù)據(jù),設(shè)置reconnectTime來展示控制客戶端重連接的時(shí)間間隔(間隔太低浪費(fèi)資源,太高又可能浪費(fèi)時(shí)間來等待)。

  3. 后端SseEmitter 的對(duì)象設(shè)置超時(shí)時(shí)間為30秒,用來展示客戶端在SSE連接超時(shí)后如何反應(yīng)。(“超時(shí)時(shí)間的大小怎么設(shè)置”要看后端傳數(shù)據(jù)一般需要的時(shí)長,太短了就會(huì)讓客戶端多次重連接后端消耗資源,太長了又可能在客戶端斷網(wǎng)、關(guān)閉頁面但未通知后端的情況下,依然讓后端維持太久的連接。)

代碼1:pom.xml中的依賴

后端只需要引入springboot的web場景。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

代碼2:后端的controller


先說一句題外話,前后端分離項(xiàng)目先解決跨域問題,自己用瀏覽器做測(cè)試也要解決跨域問題。對(duì)于Springboot框架來說,解法跨域的方案之一就是繼承WebMvcConfigurationSupport 類并注冊(cè)到容器中(加上@Configuration@Component等注冊(cè)組件),并重寫和跨域相關(guān)的方法。這樣的話Springboot會(huì)自動(dòng)讀取這個(gè)組件對(duì)跨域的設(shè)置。代碼如下

@Configuration
public class MvcConfig extends WebMvcConfigurationSupport  {
	// 重寫這個(gè)方法
    @Override
    protected void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080") // 允許本地8080端口進(jìn)行請(qǐng)求
                .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .allowCredentials(true)
                .allowedHeaders("*")
                .maxAge(3600); 
    }
}

讓后端能建立SSE連接,并每隔5秒向客戶端發(fā)送數(shù)據(jù)。

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
public class TestSSEController {
    SseEmitter emitter; // 實(shí)現(xiàn)SSE功能的類
    
    Integer backEndId=0; // 記錄后端最后發(fā)送的數(shù)據(jù)的id,用于判斷客戶端是否遺漏數(shù)據(jù)
    String backEndData=null; // 記錄后端最后發(fā)送的數(shù)據(jù),如果客戶端有遺漏數(shù)據(jù),則先發(fā)送該條數(shù)據(jù)

    @GetMapping("/test/sse")
    @ResponseBody
    public SseEmitter handle(HttpServletRequest request) throws IOException{
        emitter=new SseEmitter(30000); // 設(shè)置后端SSE連接的超時(shí)時(shí)長為30秒
        String lastId=request.getHeader("Last-Event-ID"); // 獲取客戶端收到的最后一個(gè)數(shù)據(jù)的id,用來判斷是否有漏收
        // 兩種情況意味著漏收數(shù)據(jù):
        // ①客戶端沒收到任何數(shù)據(jù),但后端記錄的最后發(fā)送的數(shù)據(jù)不為空
        // ②客戶端收到過數(shù)據(jù),但客戶端最后收到的數(shù)據(jù)id和后端最后發(fā)送的數(shù)據(jù)id不一致
        if((lastId==null && backEndData!=null) || (lastId!=null && !lastId.equals(backEndId.toString()))){
        	// 客戶端漏收了數(shù)據(jù),后端先將最新的數(shù)據(jù)返回給客戶端
            emitter.send(
                SseEmitter
                .event()
                .data(backEndData)
                .id(backEndId.toString())
                .reconnectTime(3000)
            );
        }
        return emitter;
    }

	// Springboot用@Scheduled創(chuàng)建定時(shí)任務(wù),每隔5秒自動(dòng)執(zhí)行向客戶端發(fā)送數(shù)據(jù)的函數(shù)
	// 除了要在這里加@Scheduled注解,還要在啟動(dòng)類(通常是xxxApplication類)上加@EnableScheduling注解
    @Scheduled(cron="0/5 * *  * * ? ")
    public void trigger() throws IOException{
        try{
            backEndId++; // 每次發(fā)送時(shí)id都要+1
            emitter.send(
                SseEmitter
                .event()
                .data("data"+backEndId) // 發(fā)送新數(shù)據(jù)
                .id(backEndId.toString()) // 發(fā)送新id
                .reconnectTime(3000)
            );
            backEndData="data"+backEndId; // 更新最新的數(shù)據(jù)
            System.out.println("id: "+backEndId.toString()+" data: "+backEndData);
        }catch(Exception e){
            backEndId--; 
            System.out.println("id: "+backEndId.toString()+" data: "+backEndData);
            System.out.println(e.getMessage());
        }
    }
}

代碼3:客戶端的設(shè)置

首先要做的不是打開瀏覽器在控制臺(tái)輸入javascript,而是開啟一個(gè)客戶端服務(wù)器,以便和后端通信。最簡單的開啟方式就是用python自帶的功能,可以創(chuàng)建一個(gè)位于localhost:8080的客戶端服務(wù)器(要為該路徑設(shè)置跨域,怎么設(shè)置的見上面)。

python -m http.server 8080
# 這個(gè)8080的端口號(hào)可以改成別的

開啟客戶端服務(wù)器后,打開瀏覽器訪問localhost:8080路徑,再打開控制臺(tái),用EventSource對(duì)象發(fā)起創(chuàng)建SSE連接的請(qǐng)求,并且在每次收到新數(shù)據(jù)時(shí)打印數(shù)據(jù)。

var eventSource = new EventSource("http://localhost:8000/test/sse");
eventSource.addEventListener("message", function(e) {console.log(e.data)});
// 這兩行一執(zhí)行,客戶端就會(huì)向后端發(fā)起創(chuàng)建SSE的請(qǐng)求,后端接收后會(huì)成功建立SSE。
// 之后后端每隔5秒發(fā)送數(shù)據(jù),客戶端就會(huì)把這些數(shù)據(jù)打印在控制臺(tái)中

效果1:控制臺(tái)打印的數(shù)據(jù)的樣子

Server side event (SSE)實(shí)現(xiàn)消息推送功能
后端每隔5秒發(fā)送一次數(shù)據(jù)給客戶端,客戶端拿到之后打印到控制臺(tái),所以也是差不多每隔5秒打印一次數(shù)據(jù)。

效果2:實(shí)際發(fā)起了多次SSE連接

我們將后端SSE超時(shí)時(shí)間設(shè)置為了30秒,且每5秒發(fā)送一次數(shù)據(jù),那么后端大約會(huì)發(fā)6個(gè)數(shù)據(jù),那為什么客戶端控制臺(tái)上打印了遠(yuǎn)遠(yuǎn)不止這么多的數(shù)據(jù)呢?這時(shí)候就需要看網(wǎng)絡(luò)請(qǐng)求而不是控制臺(tái)了,在開發(fā)者工具欄里點(diǎn)擊“網(wǎng)絡(luò)”,結(jié)果如下兩圖所示??梢钥吹矫恳粋€(gè)SSE請(qǐng)求只會(huì)帶來6個(gè)數(shù)據(jù),我們之所以能收到超出6個(gè)數(shù)據(jù),是因?yàn)榭蛻舳俗詣?dòng)地重連了,讓后端不斷地產(chǎn)生新的SseEmitter(忘記這回事的朋友看上文)。
Server side event (SSE)實(shí)現(xiàn)消息推送功能
Server side event (SSE)實(shí)現(xiàn)消息推送功能

效果3:客戶端斷網(wǎng)重連時(shí)補(bǔ)發(fā)數(shù)據(jù)

為了模擬客戶端斷網(wǎng)重連,后端補(bǔ)發(fā)遺漏數(shù)據(jù)的情況。我在傳送到第15個(gè)數(shù)據(jù)時(shí)啟動(dòng)了瀏覽器的離線功能來模擬斷網(wǎng),見下圖紅色圈圈那里。
Server side event (SSE)實(shí)現(xiàn)消息推送功能
此時(shí)客戶端檢測(cè)到了網(wǎng)絡(luò)問題,會(huì)每隔3秒(3秒是后端的.reconnectTime(30000)設(shè)置的)重連一次,此時(shí)控制臺(tái)會(huì)顯示以下重連信息:
Server side event (SSE)實(shí)現(xiàn)消息推送功能
后端只有在發(fā)送數(shù)據(jù)后,收到數(shù)據(jù)沒傳過去的錯(cuò)誤信息時(shí)才會(huì)意識(shí)到客戶端那邊已斷開連接,此時(shí)后端也會(huì)斷開自己的連接,SseEmitter也不能再傳數(shù)據(jù)(此時(shí)再調(diào)用SseEmitter.send()方法會(huì)拋異常的),而在此之前后端可能已經(jīng)發(fā)了好幾個(gè)新數(shù)據(jù),如下圖所示??蛻舳嗽谑盏降?5個(gè)數(shù)據(jù)的時(shí)候就已經(jīng)斷網(wǎng)了,但后端不能立即知道這件事,就又發(fā)到了第17個(gè)數(shù)據(jù),這時(shí)才收到數(shù)據(jù)傳輸?shù)腻e(cuò)誤信息,從而斷開連接,并讓SseEmitter再也不能發(fā)送任何消息。
Server side event (SSE)實(shí)現(xiàn)消息推送功能
接下來就是展示補(bǔ)發(fā)數(shù)據(jù)的過程了,如果恢復(fù)網(wǎng)絡(luò),也就是將下圖紅圈圈的下拉框選成“高速3G”,客戶端重連后端就會(huì)成功,上文后端Controller實(shí)現(xiàn)了補(bǔ)發(fā)最后一個(gè)數(shù)據(jù)的功能,此時(shí)客戶端會(huì)收到第17個(gè)數(shù)據(jù),正如下圖紅圈圈所示。如果你想要補(bǔ)發(fā)中途漏掉的所有數(shù)據(jù),那就自己修改后端的Controller。
Server side event (SSE)實(shí)現(xiàn)消息推送功能
說幾句廢話,上圖網(wǎng)絡(luò)請(qǐng)求中未成功的、標(biāo)紅的,都是客戶端重連失敗的產(chǎn)物;至于為什么上圖只有3個(gè)數(shù)據(jù)而不是預(yù)想中的6個(gè),是因?yàn)槲姨崆罢{(diào)用了客戶端中EventSource對(duì)象的close()方法,來關(guān)閉SSE連接。

線程安全問題

上面說的都是測(cè)試性的例子,沒有討論后端SseEmitter在多線程并發(fā)下的問題,這里補(bǔ)充一下:
SseEmitter對(duì)象的send()方法是線程不安全的,如果兩個(gè)線程對(duì)同一個(gè)SseEmitter對(duì)象交替使用send()方法,就可能會(huì)讓發(fā)送的數(shù)據(jù)有問題。所以,如果想實(shí)現(xiàn)線程安全,要么一個(gè)線程修改一個(gè)SseEmitter對(duì)象而不跟其他線程共用,要么自己寫一個(gè)類繼承SseEmitter,重寫send()方法時(shí)加上synchronized同步代碼塊,如下面所示

// 自己封裝一個(gè)線程安全的SseEmitter
public class SSEThreadSafeWrapper extends SseEmitter{
    public SSEThreadSafeWrapper() {
        super();
    }
    public SSEThreadSafeWrapper(long l) {
        super(l);
    }
    @Override
    public void send(SseEventBuilder arg0) throws IOException {
    // 線程安全
        synchronized(this){
            super.send(arg0);
        }
    }
    @Override
    public void send(Object object) throws IOException {
    // 線程安全
        synchronized(this){
            super.send(object);
        }
    }
    @Override
    public void send(Object object, MediaType mediaType) throws IOException {
    // 線程安全
        synchronized(this){
            super.send(object, mediaType);
        }
    }
}

然后Springboot就改一處地方:文章來源地址http://www.zghlxwxcb.cn/news/detail-456357.html

emitter=new SseEmitter(30000);
// 上面的改為下面的
emitter=new SSEThreadSafeWrapper (30000); 

到了這里,關(guān)于Server side event (SSE)實(shí)現(xiàn)消息推送功能的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • SSE(Server-Sent Events,服務(wù)器推送事件)和sockets(套接字)通信區(qū)別

    SSE(Server-Sent Events,服務(wù)器推送事件)和sockets(套接字)都是用于實(shí)現(xiàn)實(shí)時(shí)通信的技術(shù),但它們具有不同的特點(diǎn)和應(yīng)用場景。 SSE 的優(yōu)點(diǎn): 簡單易用:SSE 是基于HTTP協(xié)議的一種實(shí)時(shí)通信技術(shù),使用簡單,只需要在客戶端通過EventSource對(duì)象監(jiān)聽服務(wù)器推送的事件即可。 可靠性:

    2024年02月15日
    瀏覽(23)
  • Server-Sent Events(以下簡稱 SSE)及event-source-polyfill使用單向長連接(后臺(tái)主動(dòng)向前端推送)

    Server-Sent Events(以下簡稱 SSE)及event-source-polyfill使用單向長連接(后臺(tái)主動(dòng)向前端推送)

    SSE 與 WebSocket 作用相似,都是建立瀏覽器與服務(wù)器之間的通信渠道,然后服務(wù)器向?yàn)g覽器推送信息SSE 是單向通道,只能服務(wù)器向?yàn)g覽器發(fā)送,因?yàn)榱餍畔⒈举|(zhì)上就是下載。如果瀏覽器向服務(wù)器發(fā)送信息,就變成了另一次 HTTP 請(qǐng)求 使用方法? Server-Sent Events 教程 - 阮一峰的網(wǎng)絡(luò)

    2024年02月12日
    瀏覽(30)
  • 詳解如何通過SSE實(shí)現(xiàn)Web站內(nèi)消息推送

    Web站內(nèi)消息推送主要是通過WebSocket技術(shù)實(shí)現(xiàn)的,但是在某些情況下,WebSocket并不是最好的選擇,比如在防火墻嚴(yán)格的環(huán)境下,WebSocket可能會(huì)被阻攔。 為了解決這個(gè)問題,我們可以使用SSE技術(shù)(Server-Sent Events)。 SSE是一種輕量級(jí)的服務(wù)器推送技術(shù),可以實(shí)現(xiàn)服務(wù)器向客戶端單

    2024年02月16日
    瀏覽(21)
  • 前端實(shí)現(xiàn)消息推送、即時(shí)通信、SSE、WebSocket、http簡介

    前端實(shí)現(xiàn)消息推送、即時(shí)通信、SSE、WebSocket、http簡介

    服務(wù)端主動(dòng)向客戶端推送消息,使客戶端能夠即時(shí)接收到信息。 場景 頁面接收到點(diǎn)贊,消息提醒 聊天功能 彈幕功能 實(shí)時(shí)更新數(shù)據(jù)功能 短輪詢 瀏覽器(客戶端)每隔一段時(shí)間向服務(wù)器發(fā)送http請(qǐng)求,服務(wù)器端在收到請(qǐng)求后,不論是否有數(shù)據(jù)更新,都直接進(jìn)行響應(yīng)。 本質(zhì):客

    2024年02月16日
    瀏覽(19)
  • Springboot集成SSE實(shí)現(xiàn)消息推送之單工通信

    Springboot集成SSE實(shí)現(xiàn)消息推送之單工通信

    通常在一些web項(xiàng)目中,會(huì)涉及到想客戶端推送消息,常見的有Ajax輪詢、webSocket,本篇文章主要使用Springboot集成SSE實(shí)現(xiàn)向客戶端持續(xù)推送信息。 服務(wù)發(fā)送事件SSE(Sever-Sent Event),就是基于 HTTP 的技術(shù),瀏覽器向服務(wù)器發(fā)送一個(gè)保持長連接HTTP請(qǐng)求,服務(wù)器單向地向客戶端以流形

    2024年01月17日
    瀏覽(25)
  • SSE實(shí)現(xiàn)消息實(shí)時(shí)推送,前端漸進(jìn)式學(xué)習(xí)、實(shí)踐,真香

    SSE(Server Sent Event),直譯為服務(wù)器發(fā)送事件,顧名思義,也就是客戶端可以獲取到服務(wù)器發(fā)送的事件。我們常見的 http 交互方式是客戶端發(fā)起請(qǐng)求,服務(wù)端響應(yīng),然后一次請(qǐng)求完畢;但是在 sse 的場景下,客戶端發(fā)起請(qǐng)求,連接一直保持,服務(wù)端有數(shù)據(jù)就可以返回?cái)?shù)據(jù)給客戶端

    2024年02月21日
    瀏覽(20)
  • Server-Sent Events(SSE) 入門、原理、介紹、類ChatGpt流式輸出實(shí)現(xiàn)

    一、引言 在現(xiàn)代Web應(yīng)用程序中,實(shí)時(shí)數(shù)據(jù)傳輸和實(shí)時(shí)通信變得越來越重要。為了實(shí)現(xiàn)這種實(shí)時(shí)通信,多種技術(shù)應(yīng)運(yùn)而生,如WebSocket、長輪詢和Server-Sent Events(SSE)。在本文中,我們將重點(diǎn)探討Server-Sent Events,一種基于HTTP的實(shí)時(shí)通信協(xié)議。 二、技術(shù)背景 Server-Sent Events(SSE)它

    2024年02月08日
    瀏覽(35)
  • SSE[Server-Sent Events]實(shí)現(xiàn)頁面流式數(shù)據(jù)輸出(模擬ChatGPT流式輸出)

    SSE[Server-Sent Events]實(shí)現(xiàn)頁面流式數(shù)據(jù)輸出(模擬ChatGPT流式輸出)

    ????????服務(wù)端向客戶端推送消息,除了用WebSocket可實(shí)現(xiàn),還有一種服務(wù)器發(fā)送事件(Server-Sent Events)簡稱 SSE,這是一種服務(wù)器端到客戶端(瀏覽器)的單向消息推送。ChatGPT 就是采用的 SSE。對(duì)于需要長時(shí)間等待響應(yīng)的對(duì)話場景,ChatGPT 采用了一種巧妙的策略:它會(huì)將已經(jīng)計(jì)算

    2024年01月22日
    瀏覽(29)
  • 如果讓你實(shí)現(xiàn)實(shí)時(shí)消息推送你會(huì)用什么技術(shù)?輪詢、websocket還是sse

    如果讓你實(shí)現(xiàn)實(shí)時(shí)消息推送你會(huì)用什么技術(shù)?輪詢、websocket還是sse

    在日常的開發(fā)中,我們經(jīng)常能碰見服務(wù)端需要主動(dòng)推送給客戶端數(shù)據(jù)的業(yè)務(wù)場景,比如_數(shù)據(jù)大屏的實(shí)時(shí)數(shù)據(jù)_,比如_消息中心的未讀消息_,比如_聊天功能_等等。 本文主要介紹SSE的使用場景和如何使用SSE。 學(xué)習(xí)就完事了 我們常規(guī)實(shí)現(xiàn)這些需求的方案有以下三種 輪詢 websock

    2024年03月19日
    瀏覽(31)
  • Spring Boot 整合 SSE(Server Sent Event)

    服務(wù)器發(fā)送事件(Server-Sent Events),簡稱 SSE。這是一種服務(wù)器端到客戶端的單向消息推送。SSE 基于 HTTP 協(xié)議的,SSE 在服務(wù)器和客戶端之間打開一個(gè)單向通道,服務(wù)端響應(yīng)的不再是一次性的數(shù)據(jù)包而是text/event-stream類型的數(shù)據(jù)流信息 后端代碼: 細(xì)節(jié): 創(chuàng)建SseEmitter 對(duì)象時(shí)需要返

    2024年02月16日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包