欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java實(shí)現(xiàn)web實(shí)時(shí)消息推送的七種方案

 更新時(shí)間:2022年07月23日 11:27:16   作者:程序員小富  
這篇文章主要為大家介紹了java實(shí)現(xiàn)web實(shí)時(shí)消息推送的七種方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

做了一個(gè)小破站,現(xiàn)在要實(shí)現(xiàn)一個(gè)站內(nèi)信web消息推送的功能,對(duì),就是下圖這個(gè)小紅點(diǎn),一個(gè)很常用的功能。

不過(guò)他還沒(méi)想好用什么方式做,這里我?guī)退砹艘幌聨追N方案,并簡(jiǎn)單做了實(shí)現(xiàn)。

案例下載

什么是消息推送(push)

推送的場(chǎng)景比較多,比如有人關(guān)注我的公眾號(hào),這時(shí)我就會(huì)收到一條推送消息,以此來(lái)吸引我點(diǎn)擊打開(kāi)應(yīng)用。

消息推送(push)通常是指網(wǎng)站的運(yùn)營(yíng)工作等人員,通過(guò)某種工具對(duì)用戶(hù)當(dāng)前網(wǎng)頁(yè)或移動(dòng)設(shè)備APP進(jìn)行的主動(dòng)消息推送。

消息推送一般又分為web端消息推送移動(dòng)端消息推送。

上邊的這種屬于移動(dòng)端消息推送,web端消息推送常見(jiàn)的諸如站內(nèi)信、未讀郵件數(shù)量、監(jiān)控報(bào)警數(shù)量等,應(yīng)用的也非常廣泛。

在具體實(shí)現(xiàn)之前,咱們?cè)賮?lái)分析一下前邊的需求,其實(shí)功能很簡(jiǎn)單,只要觸發(fā)某個(gè)事件(主動(dòng)分享了資源或者后臺(tái)主動(dòng)推送消息),web頁(yè)面的通知小紅點(diǎn)就會(huì)實(shí)時(shí)的+1就可以了。

通常在服務(wù)端會(huì)有若干張消息推送表,用來(lái)記錄用戶(hù)觸發(fā)不同事件所推送不同類(lèi)型的消息,前端主動(dòng)查詢(xún)(拉)或者被動(dòng)接收(推)用戶(hù)所有未讀的消息數(shù)。

消息推送無(wú)非是推(push)和拉(pull)兩種形式,下邊我們逐個(gè)了解下。

短輪詢(xún)

輪詢(xún)(polling)應(yīng)該是實(shí)現(xiàn)消息推送方案中最簡(jiǎn)單的一種,這里我們暫且將輪詢(xún)分為短輪詢(xún)長(zhǎng)輪詢(xún)。

短輪詢(xún)很好理解,指定的時(shí)間間隔,由瀏覽器向服務(wù)器發(fā)出HTTP請(qǐng)求,服務(wù)器實(shí)時(shí)返回未讀消息數(shù)據(jù)給客戶(hù)端,瀏覽器再做渲染顯示。

一個(gè)簡(jiǎn)單的JS定時(shí)器就可以搞定,每秒鐘請(qǐng)求一次未讀消息數(shù)接口,返回的數(shù)據(jù)展示即可。

setInterval(() => {
  // 方法請(qǐng)求
  messageCount().then((res) => {
      if (res.code === 200) {
          this.messageCount = res.data
      }
  })
}, 1000);

效果還是可以的,短輪詢(xún)實(shí)現(xiàn)固然簡(jiǎn)單,缺點(diǎn)也是顯而易見(jiàn),由于推送數(shù)據(jù)并不會(huì)頻繁變更,無(wú)論后端此時(shí)是否有新的消息產(chǎn)生,客戶(hù)端都會(huì)進(jìn)行請(qǐng)求,勢(shì)必會(huì)對(duì)服務(wù)端造成很大壓力,浪費(fèi)帶寬和服務(wù)器資源。

長(zhǎng)輪詢(xún)

長(zhǎng)輪詢(xún)是對(duì)上邊短輪詢(xún)的一種改進(jìn)版本,在盡可能減少對(duì)服務(wù)器資源浪費(fèi)的同時(shí),保證消息的相對(duì)實(shí)時(shí)性。長(zhǎng)輪詢(xún)?cè)谥虚g件中應(yīng)用的很廣泛,比如Nacosapollo配置中心,消息隊(duì)列kafkaRocketMQ中都有用到長(zhǎng)輪詢(xún)。

Nacos配置中心交互模型是push還是pull?一文中我詳細(xì)介紹過(guò)Nacos長(zhǎng)輪詢(xún)的實(shí)現(xiàn)原理,感興趣的小伙伴可以瞅瞅。

這次我使用apollo配置中心實(shí)現(xiàn)長(zhǎng)輪詢(xún)的方式,應(yīng)用了一個(gè)類(lèi)

DeferredResult可以允許容器線(xiàn)程快速釋放占用的資源,不阻塞請(qǐng)求線(xiàn)程,以此接受更多的請(qǐng)求提升系統(tǒng)的吞吐量,然后啟動(dòng)異步工作線(xiàn)程處理真正的業(yè)務(wù)邏輯,處理完成調(diào)用DeferredResult.setResult(200)提交響應(yīng)結(jié)果。

下邊我們用長(zhǎng)輪詢(xún)來(lái)實(shí)現(xiàn)消息推送。

因?yàn)橐粋€(gè)ID可能會(huì)被多個(gè)長(zhǎng)輪詢(xún)請(qǐng)求監(jiān)聽(tīng),所以我采用了guava包提供的Multimap結(jié)構(gòu)存放長(zhǎng)輪詢(xún),一個(gè)key可以對(duì)應(yīng)多個(gè)value。一旦監(jiān)聽(tīng)到key發(fā)生變化,對(duì)應(yīng)的所有長(zhǎng)輪詢(xún)都會(huì)響應(yīng)。前端得到非請(qǐng)求超時(shí)的狀態(tài)碼,知曉數(shù)據(jù)變更,主動(dòng)查詢(xún)未讀消息數(shù)接口,更新頁(yè)面數(shù)據(jù)。

@Controller
@RequestMapping("/polling")
public class PollingController {
    // 存放監(jiān)聽(tīng)某個(gè)Id的長(zhǎng)輪詢(xún)集合
    // 線(xiàn)程同步結(jié)構(gòu)
    public static Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedMultimap(HashMultimap.create());
    /**
     * 公眾號(hào):程序員小富
     * 設(shè)置監(jiān)聽(tīng)
     */
    @GetMapping(path = "watch/{id}")
    @ResponseBody
    public DeferredResult<String> watch(@PathVariable String id) {
        // 延遲對(duì)象設(shè)置超時(shí)時(shí)間
        DeferredResult<String> deferredResult = new DeferredResult<>(TIME_OUT);
        // 異步請(qǐng)求完成時(shí)移除 key,防止內(nèi)存溢出
        deferredResult.onCompletion(() -> {
            watchRequests.remove(id, deferredResult);
        });
        // 注冊(cè)長(zhǎng)輪詢(xún)請(qǐng)求
        watchRequests.put(id, deferredResult);
        return deferredResult;
    }
    /**
     * 公眾號(hào):程序員小富
     * 變更數(shù)據(jù)
     */
    @GetMapping(path = "publish/{id}")
    @ResponseBody
    public String publish(@PathVariable String id) {
        // 數(shù)據(jù)變更 取出監(jiān)聽(tīng)I(yíng)D的所有長(zhǎng)輪詢(xún)請(qǐng)求,并一一響應(yīng)處理
        if (watchRequests.containsKey(id)) {
            Collection<DeferredResult<String>> deferredResults = watchRequests.get(id);
            for (DeferredResult<String> deferredResult : deferredResults) {
                deferredResult.setResult("我更新了" + new Date());
            }
        }
        return "success";
    }

當(dāng)請(qǐng)求超過(guò)設(shè)置的超時(shí)時(shí)間,會(huì)拋出AsyncRequestTimeoutException異常,這里直接用@ControllerAdvice全局捕獲統(tǒng)一返回即可,前端獲取約定好的狀態(tài)碼后再次發(fā)起長(zhǎng)輪詢(xún)請(qǐng)求,如此往復(fù)調(diào)用。

@ControllerAdvice
public class AsyncRequestTimeoutHandler {
    @ResponseStatus(HttpStatus.NOT_MODIFIED)
    @ResponseBody
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {
        System.out.println("異步請(qǐng)求超時(shí)");
        return "304";
    }
}

我們來(lái)測(cè)試一下,首先頁(yè)面發(fā)起長(zhǎng)輪詢(xún)請(qǐng)求/polling/watch/10086監(jiān)聽(tīng)消息更變,請(qǐng)求被掛起,不變更數(shù)據(jù)直至超時(shí),再次發(fā)起了長(zhǎng)輪詢(xún)請(qǐng)求;緊接著手動(dòng)變更數(shù)據(jù)/polling/publish/10086,長(zhǎng)輪詢(xún)得到響應(yīng),前端處理業(yè)務(wù)邏輯完成后再次發(fā)起請(qǐng)求,如此循環(huán)往復(fù)。

長(zhǎng)輪詢(xún)相比于短輪詢(xún)?cè)谛阅苌咸嵘撕芏?,但依然?huì)產(chǎn)生較多的請(qǐng)求,這是它的一點(diǎn)不完美的地方。

iframe流

iframe流就是在頁(yè)面中插入一個(gè)隱藏的<iframe>標(biāo)簽,通過(guò)在src中請(qǐng)求消息數(shù)量API接口,由此在服務(wù)端和客戶(hù)端之間創(chuàng)建一條長(zhǎng)連接,服務(wù)端持續(xù)向iframe傳輸數(shù)據(jù)。

傳輸?shù)臄?shù)據(jù)通常是HTML、或是內(nèi)嵌的javascript腳本,來(lái)達(dá)到實(shí)時(shí)更新頁(yè)面的效果。

這種方式實(shí)現(xiàn)簡(jiǎn)單,前端只要一個(gè)<iframe>標(biāo)簽搞定了

<iframe src="/iframe/message" style="display:none"></iframe>

服務(wù)端直接組裝html、js腳本數(shù)據(jù)向response寫(xiě)入就行了

@Controller
@RequestMapping("/iframe")
public class IframeController {
    @GetMapping(path = "message")
    public void message(HttpServletResponse response) throws IOException, InterruptedException {
        while (true) {
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setHeader("Cache-Control", "no-cache,no-store");
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().print(" <script type=\"text/javascript\">\n" +
                    "parent.document.getElementById('clock').innerHTML = \"" + count.get() + "\";" +
                    "parent.document.getElementById('count').innerHTML = \"" + count.get() + "\";" +
                    "</script>");
        }
    }
}

但我個(gè)人不推薦,因?yàn)樗跒g覽器上會(huì)顯示請(qǐng)求未加載完,圖標(biāo)會(huì)不停旋轉(zhuǎn),簡(jiǎn)直是強(qiáng)迫癥殺手。

SSE (我的方式)

很多人可能不知道,服務(wù)端向客戶(hù)端推送消息,其實(shí)除了可以用WebSocket這種耳熟能詳?shù)臋C(jī)制外,還有一種服務(wù)器發(fā)送事件(Server-sent events),簡(jiǎn)稱(chēng)SSE。

SSE它是基于HTTP協(xié)議的,我們知道一般意義上的HTTP協(xié)議是無(wú)法做到服務(wù)端主動(dòng)向客戶(hù)端推送消息的,但SSE是個(gè)例外,它變換了一種思路。

SSE在服務(wù)器和客戶(hù)端之間打開(kāi)一個(gè)單向通道,服務(wù)端響應(yīng)的不再是一次性的數(shù)據(jù)包而是text/event-stream類(lèi)型的數(shù)據(jù)流信息,在有數(shù)據(jù)變更時(shí)從服務(wù)器流式傳輸?shù)娇蛻?hù)端。

整體的實(shí)現(xiàn)思路有點(diǎn)類(lèi)似于在線(xiàn)視頻播放,視頻流會(huì)連續(xù)不斷的推送到瀏覽器,你也可以理解成,客戶(hù)端在完成一次用時(shí)很長(zhǎng)(網(wǎng)絡(luò)不暢)的下載。

SSEWebSocket作用相似,都可以建立服務(wù)端與瀏覽器之間的通信,實(shí)現(xiàn)服務(wù)端向客戶(hù)端推送消息,但還是有些許不同:

  • SSE 是基于HTTP協(xié)議的,它們不需要特殊的協(xié)議或服務(wù)器實(shí)現(xiàn)即可工作;WebSocket需單獨(dú)服務(wù)器來(lái)處理協(xié)議。
  • SSE 單向通信,只能由服務(wù)端向客戶(hù)端單向通信;webSocket全雙工通信,即通信的雙方可以同時(shí)發(fā)送和接受信息。
  • SSE 實(shí)現(xiàn)簡(jiǎn)單開(kāi)發(fā)成本低,無(wú)需引入其他組件;WebSocket傳輸數(shù)據(jù)需做二次解析,開(kāi)發(fā)門(mén)檻高一些。
  • SSE 默認(rèn)支持?jǐn)嗑€(xiàn)重連;WebSocket則需要自己實(shí)現(xiàn)。
  • SSE 只能傳送文本消息,二進(jìn)制數(shù)據(jù)需要經(jīng)過(guò)編碼后傳送;WebSocket默認(rèn)支持傳送二進(jìn)制數(shù)據(jù)。

SSE 與 WebSocket 該如何選擇?

技術(shù)并沒(méi)有好壞之分,只有哪個(gè)更合適

SSE好像一直不被大家所熟知,一部分原因是出現(xiàn)了WebSockets,這個(gè)提供了更豐富的協(xié)議來(lái)執(zhí)行雙向、全雙工通信。對(duì)于游戲、即時(shí)通信以及需要雙向近乎實(shí)時(shí)更新的場(chǎng)景,擁有雙向通道更具吸引力。

但是,在某些情況下,不需要從客戶(hù)端發(fā)送數(shù)據(jù)。而你只需要一些服務(wù)器操作的更新。比如:站內(nèi)信、未讀消息數(shù)、狀態(tài)更新、股票行情、監(jiān)控?cái)?shù)量等場(chǎng)景,SEE不管是從實(shí)現(xiàn)的難易和成本上都更加有優(yōu)勢(shì)。此外,SSE 具有WebSockets在設(shè)計(jì)上缺乏的多種功能,例如:自動(dòng)重新連接事件ID發(fā)送任意事件的能力。

前端只需進(jìn)行一次HTTP請(qǐng)求,帶上唯一ID,打開(kāi)事件流,監(jiān)聽(tīng)服務(wù)端推送的事件就可以了

<script>
    let source = null;
    let userId = 7777
    if (window.EventSource) {
        // 建立連接
        source = new EventSource('http://localhost:7777/sse/sub/'+userId);
        setMessageInnerHTML("連接用戶(hù)=" + userId);
        /**
         * 連接一旦建立,就會(huì)觸發(fā)open事件
         * 另一種寫(xiě)法:source.onopen = function (event) {}
         */
        source.addEventListener('open', function (e) {
            setMessageInnerHTML("建立連接。。。");
        }, false);
        /**
         * 客戶(hù)端收到服務(wù)器發(fā)來(lái)的數(shù)據(jù)
         * 另一種寫(xiě)法:source.onmessage = function (event) {}
         */
        source.addEventListener('message', function (e) {
            setMessageInnerHTML(e.data);
        });
    } else {
        setMessageInnerHTML("你的瀏覽器不支持SSE");
    }
</script>

服務(wù)端的實(shí)現(xiàn)更簡(jiǎn)單,創(chuàng)建一個(gè)SseEmitter對(duì)象放入sseEmitterMap進(jìn)行管理

private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
/**
 * 創(chuàng)建連接
 *
 * @date: 2022/7/12 14:51
 * @auther: 公眾號(hào):程序員小富
 */
public static SseEmitter connect(String userId) {
    try {
        // 設(shè)置超時(shí)時(shí)間,0表示不過(guò)期。默認(rèn)30秒
        SseEmitter sseEmitter = new SseEmitter(0L);
        // 注冊(cè)回調(diào)
        sseEmitter.onCompletion(completionCallBack(userId));
        sseEmitter.onError(errorCallBack(userId));
        sseEmitter.onTimeout(timeoutCallBack(userId));
        sseEmitterMap.put(userId, sseEmitter);
        count.getAndIncrement();
        return sseEmitter;
    } catch (Exception e) {
        log.info("創(chuàng)建新的sse連接異常,當(dāng)前用戶(hù):{}", userId);
    }
    return null;
}
/**
 * 給指定用戶(hù)發(fā)送消息
 *
 * @date: 2022/7/12 14:51
 * @auther: 公眾號(hào):程序員小富
 */
public static void sendMessage(String userId, String message) {
    if (sseEmitterMap.containsKey(userId)) {
        try {
            sseEmitterMap.get(userId).send(message);
        } catch (IOException e) {
            log.error("用戶(hù)[{}]推送異常:{}", userId, e.getMessage());
            removeUser(userId);
        }
    }
}

我們模擬服務(wù)端推送消息,看下客戶(hù)端收到了消息,和我們預(yù)期的效果一致。 

注意: SSE不支持IE瀏覽器,對(duì)其他主流瀏覽器兼容性做的還不錯(cuò)。 

MQTT

什么是 MQTT協(xié)議?

MQTT 全稱(chēng)(Message Queue Telemetry Transport):一種基于發(fā)布/訂閱(publish/subscribe)模式的輕量級(jí)通訊協(xié)議,通過(guò)訂閱相應(yīng)的主題來(lái)獲取消息,是物聯(lián)網(wǎng)(Internet of Thing)中的一個(gè)標(biāo)準(zhǔn)傳輸協(xié)議。

該協(xié)議將消息的發(fā)布者(publisher)與訂閱者(subscriber)進(jìn)行分離,因此可以在不可靠的網(wǎng)絡(luò)環(huán)境中,為遠(yuǎn)程連接的設(shè)備提供可靠的消息服務(wù),使用方式與傳統(tǒng)的MQ有點(diǎn)類(lèi)似。

TCP協(xié)議位于傳輸層,MQTT 協(xié)議位于應(yīng)用層,MQTT 協(xié)議構(gòu)建于TCP/IP協(xié)議上,也就是說(shuō)只要支持TCP/IP協(xié)議棧的地方,都可以使用MQTT協(xié)議。

為什么要用 MQTT協(xié)議?

MQTT協(xié)議為什么在物聯(lián)網(wǎng)(IOT)中如此受偏愛(ài)?而不是其它協(xié)議,比如我們更為熟悉的 HTTP協(xié)議呢?

  • 首先HTTP協(xié)議它是一種同步協(xié)議,客戶(hù)端請(qǐng)求后需要等待服務(wù)器的響應(yīng)。而在物聯(lián)網(wǎng)(IOT)環(huán)境中,設(shè)備會(huì)很受制于環(huán)境的影響,比如帶寬低、網(wǎng)絡(luò)延遲高、網(wǎng)絡(luò)通信不穩(wěn)定等,顯然異步消息協(xié)議更為適合IOT應(yīng)用程序。
  • HTTP是單向的,如果要獲取消息客戶(hù)端必須發(fā)起連接,而在物聯(lián)網(wǎng)(IOT)應(yīng)用程序中,設(shè)備或傳感器往往都是客戶(hù)端,這意味著它們無(wú)法被動(dòng)地接收來(lái)自網(wǎng)絡(luò)的命令。
  • 通常需要將一條命令或者消息,發(fā)送到網(wǎng)絡(luò)上的所有設(shè)備上。HTTP要實(shí)現(xiàn)這樣的功能不但很困難,而且成本極高。

具體的MQTT協(xié)議介紹和實(shí)踐,這里我就不再贅述了,大家可以參考我之前的兩篇文章,里邊寫(xiě)的也都很詳細(xì)了。

MQTT協(xié)議的介紹

我也沒(méi)想到 springboot + rabbitmq 做智能家居,會(huì)這么簡(jiǎn)單

MQTT實(shí)現(xiàn)消息推送

未讀消息(小紅點(diǎn)),前端 與 RabbitMQ 實(shí)時(shí)消息推送實(shí)踐,賊簡(jiǎn)單~

Websocket

websocket應(yīng)該是大家都比較熟悉的一種實(shí)現(xiàn)消息推送的方式,上邊我們?cè)谥vSSE的時(shí)候也和websocket進(jìn)行過(guò)比較。

WebSocket是一種在TCP連接上進(jìn)行全雙工通信的協(xié)議,建立客戶(hù)端和服務(wù)器之間的通信渠道。瀏覽器和服務(wù)器僅需一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

springboot整合websocket,先引入websocket相關(guān)的工具包,和SSE相比額外的開(kāi)發(fā)成本。

<!-- 引入websocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

服務(wù)端使用@ServerEndpoint注解標(biāo)注當(dāng)前類(lèi)為一個(gè)websocket服務(wù)器,客戶(hù)端可以通過(guò)ws://localhost:7777/webSocket/10086來(lái)連接到WebSocket服務(wù)器端。

@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
    //與某個(gè)客戶(hù)端的連接會(huì)話(huà),需要通過(guò)它來(lái)給客戶(hù)端發(fā)送數(shù)據(jù)
    private Session session;
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
    // 用來(lái)存在線(xiàn)連接數(shù)
    private static final Map<String, Session> sessionPool = new HashMap<String, Session>();
    /**
     * 公眾號(hào):程序員小富
     * 鏈接成功調(diào)用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        try {
            this.session = session;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info("websocket消息: 有新的連接,總數(shù)為:" + webSockets.size());
        } catch (Exception e) {
        }
    }
    /**
     * 公眾號(hào):程序員小富
     * 收到客戶(hù)端消息后調(diào)用的方法
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("websocket消息: 收到客戶(hù)端消息:" + message);
    }
    /**
     * 公眾號(hào):程序員小富
     * 此為單點(diǎn)消息
     */
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("websocket消: 單點(diǎn)消息:" + message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

前端初始化打開(kāi)WebSocket連接,并監(jiān)聽(tīng)連接狀態(tài),接收服務(wù)端數(shù)據(jù)或向服務(wù)端發(fā)送數(shù)據(jù)。

<script>
    var ws = new WebSocket('ws://localhost:7777/webSocket/10086');
    // 獲取連接狀態(tài)
    console.log('ws連接狀態(tài):' + ws.readyState);
    //監(jiān)聽(tīng)是否連接成功
    ws.onopen = function () {
        console.log('ws連接狀態(tài):' + ws.readyState);
        //連接成功則發(fā)送一個(gè)數(shù)據(jù)
        ws.send('test1');
    }
    // 接聽(tīng)服務(wù)器發(fā)回的信息并處理展示
    ws.onmessage = function (data) {
        console.log('接收到來(lái)自服務(wù)器的消息:');
        console.log(data);
        //完成通信后關(guān)閉WebSocket連接
        ws.close();
    }
    // 監(jiān)聽(tīng)連接關(guān)閉事件
    ws.onclose = function () {
        // 監(jiān)聽(tīng)整個(gè)過(guò)程中websocket的狀態(tài)
        console.log('ws連接狀態(tài):' + ws.readyState);
    }
    // 監(jiān)聽(tīng)并處理error事件
    ws.onerror = function (error) {
        console.log(error);
    }
    function sendMessage() {
        var content = $("#message").val();
        $.ajax({
            url: '/socket/publish?userId=10086&message=' + content,
            type: 'GET',
            data: { "id": "7777", "content": content },
            success: function (data) {
                console.log(data)
            }
        })
    }
</script>

頁(yè)面初始化建立websocket連接,之后就可以進(jìn)行雙向通信了,效果還不錯(cuò)

>

自定義推送

上邊我們給我出了6種方案的原理和代碼實(shí)現(xiàn),但在實(shí)際業(yè)務(wù)開(kāi)發(fā)過(guò)程中,不能盲目的直接拿過(guò)來(lái)用,還是要結(jié)合自身系統(tǒng)業(yè)務(wù)的特點(diǎn)和實(shí)際場(chǎng)景來(lái)選擇合適的方案。

推送最直接的方式就是使用第三推送平臺(tái),畢竟錢(qián)能解決的需求都不是問(wèn)題,無(wú)需復(fù)雜的開(kāi)發(fā)運(yùn)維,直接可以使用,省時(shí)、省力、省心,像goEasy、極光推送都是很不錯(cuò)的三方服務(wù)商。

一般大型公司都有自研的消息推送平臺(tái),像我們本次實(shí)現(xiàn)的web站內(nèi)信只是平臺(tái)上的一個(gè)觸點(diǎn)而已,短信、郵件、微信公眾號(hào)、小程序凡是可以觸達(dá)到用戶(hù)的渠道都可以接入進(jìn)來(lái)。

消息推送系統(tǒng)內(nèi)部是相當(dāng)復(fù)雜的,諸如消息內(nèi)容的維護(hù)審核、圈定推送人群、觸達(dá)過(guò)濾攔截(推送的規(guī)則頻次、時(shí)段、數(shù)量、黑白名單、關(guān)鍵詞等等)、推送失敗補(bǔ)償非常多的模塊,技術(shù)上涉及到大數(shù)據(jù)量、高并發(fā)的場(chǎng)景也很多。所以我們今天的實(shí)現(xiàn)方式在這個(gè)龐大的系統(tǒng)面前只是小打小鬧。

Github地址

文中所提到的案例我都一一的做了實(shí)現(xiàn),整理放在了Github上,覺(jué)得有用就 Star 一下吧!

傳送門(mén):github.com/chengxy-nds…

以上就是java實(shí)現(xiàn)web實(shí)時(shí)消息推送的七種方案的詳細(xì)內(nèi)容,更多關(guān)于java web實(shí)時(shí)消息推送的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java中Websocket的使用方法例子

    java中Websocket的使用方法例子

    這篇文章主要給大家介紹了關(guān)于java中Websocket的使用方法,WebSocket是HTML5開(kāi)始提供的一種在瀏覽器和服務(wù)器間進(jìn)行全雙工通信的協(xié)議,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • IDEA創(chuàng)建springboot + mybatis項(xiàng)目全過(guò)程(步驟詳解)

    IDEA創(chuàng)建springboot + mybatis項(xiàng)目全過(guò)程(步驟詳解)

    這篇文章主要介紹了IDEA創(chuàng)建springboot + mybatis項(xiàng)目全過(guò)程及步驟詳解,本文通圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • springboot application.yml使用@@pom文件配置問(wèn)題

    springboot application.yml使用@@pom文件配置問(wèn)題

    這篇文章主要介紹了springboot application.yml使用@@pom文件配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • IntelliJ IDEA2019 安裝lombok的實(shí)現(xiàn)

    IntelliJ IDEA2019 安裝lombok的實(shí)現(xiàn)

    這篇文章主要介紹了IntelliJ IDEA2019 安裝lombok的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • JVM內(nèi)存模型/內(nèi)存空間:運(yùn)行時(shí)數(shù)據(jù)區(qū)

    JVM內(nèi)存模型/內(nèi)存空間:運(yùn)行時(shí)數(shù)據(jù)區(qū)

    這篇文章主要介紹了JVM內(nèi)存模型/內(nèi)存空間的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java虛擬機(jī),感興趣的朋友可以了解詳細(xì),希望能夠給你帶來(lái)幫助
    2021-08-08
  • java獲取網(wǎng)絡(luò)類(lèi)型的方法

    java獲取網(wǎng)絡(luò)類(lèi)型的方法

    這篇文章主要介紹了java獲取網(wǎng)絡(luò)類(lèi)型的方法,涉及java針對(duì)網(wǎng)絡(luò)類(lèi)型的參數(shù)獲取及判定技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10
  • maven私服的配置使用方法

    maven私服的配置使用方法

    這篇文章主要介紹了maven私服的配置使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 一文詳解SpringBoot如何優(yōu)雅地實(shí)現(xiàn)異步調(diào)用

    一文詳解SpringBoot如何優(yōu)雅地實(shí)現(xiàn)異步調(diào)用

    SpringBoot想必大家都用過(guò),但是大家平時(shí)使用發(fā)布的接口大都是同步的,那么你知道如何優(yōu)雅的實(shí)現(xiàn)異步呢?這篇文章就來(lái)和大家詳細(xì)聊聊
    2023-03-03
  • java中快速創(chuàng)建帶初始值的List和Map實(shí)例

    java中快速創(chuàng)建帶初始值的List和Map實(shí)例

    下面小編就為大家?guī)?lái)一篇java中快速創(chuàng)建帶初始值的List和Map實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • MongoDB整合Spring實(shí)例詳細(xì)講解(含代碼)

    MongoDB整合Spring實(shí)例詳細(xì)講解(含代碼)

    這篇文章主要介紹了MongoDB整合Spring實(shí)例詳細(xì)講解(含代碼),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-01-01

最新評(píng)論