久久国产精品一区二区三区四区,久色婷婷小香蕉久久,国产日韩欧美在线播放不卡,另类av一区二区

環球實時:我有七種實現Web實時消息推送的方案
來源:程序員小富    時間:2022-07-30 09:51:19

我有一個朋友~


(資料圖)

做了一個小破站,現在要實現一個站內信web消息推送的功能,對,就是下圖這個小紅點,一個很常用的功能。

不過他還沒想好用什么方式做,這里我幫他整理了一下幾種方案,并簡單做了實現。

什么是消息推送(push)

推送的場景比較多,比如有人關注我的公眾號,這時我就會收到一條推送消息,以此來吸引我點擊打開應用。

消息推送(push)通常是指網站的運營工作等人員,通過某種工具對用戶當前網頁或移動設備APP進行的主動消息推送。

消息推送一般又分為web端消息推送?和移動端消息推送。

上邊的這種屬于移動端消息推送,web端消息推送常見的諸如站內信、未讀郵件數量、監控報警數量等,應用的也非常廣泛。

在具體實現之前,咱們再來分析一下前邊的需求,其實功能很簡單,只要觸發某個事件(主動分享了資源或者后臺主動推送消息),web頁面的通知小紅點就會實時的+1就可以了。

通常在服務端會有若干張消息推送表,用來記錄用戶觸發不同事件所推送不同類型的消息,前端主動查詢(拉)或者被動接收(推)用戶所有未讀的消息數。

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

短輪詢

輪詢(polling?)應該是實現消息推送方案中最簡單的一種,這里我們暫且將輪詢分為短輪詢?和長輪詢。

短輪詢很好理解,指定的時間間隔,由瀏覽器向服務器發出HTTP請求,服務器實時返回未讀消息數據給客戶端,瀏覽器再做渲染顯示。

一個簡單的JS定時器就可以搞定,每秒鐘請求一次未讀消息數接口,返回的數據展示即可。

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

效果還是可以的,短輪詢實現固然簡單,缺點也是顯而易見,由于推送數據并不會頻繁變更,無論后端此時是否有新的消息產生,客戶端都會進行請求,勢必會對服務端造成很大壓力,浪費帶寬和服務器資源。

長輪詢

長輪詢是對上邊短輪詢的一種改進版本,在盡可能減少對服務器資源浪費的同時,保證消息的相對實時性。長輪詢在中間件中應用的很廣泛,比如Nacos?和apollo?配置中心,消息隊列kafka?、RocketMQ中都有用到長輪詢。

Nacos配置中心交互模型是push還是pull??一文中我詳細介紹過Nacos長輪詢的實現原理,感興趣的小伙伴可以瞅瞅。

這次我使用apollo?配置中心實現長輪詢的方式,應用了一個類DeferredResult?,它是在servelet3.0后經過Spring封裝提供的一種異步請求機制,直意就是延遲結果。

DeferredResult?可以允許容器線程快速釋放占用的資源,不阻塞請求線程,以此接受更多的請求提升系統的吞吐量,然后啟動異步工作線程處理真正的業務邏輯,處理完成調用DeferredResult.setResult(200)提交響應結果。

下邊我們用長輪詢來實現消息推送。

因為一個ID可能會被多個長輪詢請求監聽,所以我采用了guava?包提供的Multimap結構存放長輪詢,一個key可以對應多個value。一旦監聽到key發生變化,對應的所有長輪詢都會響應。前端得到非請求超時的狀態碼,知曉數據變更,主動查詢未讀消息數接口,更新頁面數據。

@Controller@RequestMapping("/polling")public class PollingController { // 存放監聽某個Id的長輪詢集合 // 線程同步結構 public static Multimap> watchRequests = Multimaps.synchronizedMultimap(HashMultimap.create()); /** * 公眾號:程序員小富 * 設置監聽 */ @GetMapping(path = "watch/{id}") @ResponseBody public DeferredResult watch(@PathVariable String id) { // 延遲對象設置超時時間 DeferredResult deferredResult = new DeferredResult<>(TIME_OUT); // 異步請求完成時移除 key,防止內存溢出 deferredResult.onCompletion(() -> { watchRequests.remove(id, deferredResult); }); // 注冊長輪詢請求 watchRequests.put(id, deferredResult); return deferredResult; } /** * 公眾號:程序員小富 * 變更數據 */ @GetMapping(path = "publish/{id}") @ResponseBody public String publish(@PathVariable String id) { // 數據變更 取出監聽ID的所有長輪詢請求,并一一響應處理 if (watchRequests.containsKey(id)) { Collection> deferredResults = watchRequests.get(id); for (DeferredResult deferredResult : deferredResults) { deferredResult.setResult("我更新了" + new Date()); } } return "success"; }

當請求超過設置的超時時間,會拋出AsyncRequestTimeoutException?異常,這里直接用@ControllerAdvice全局捕獲統一返回即可,前端獲取約定好的狀態碼后再次發起長輪詢請求,如此往復調用。

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

我們來測試一下,首先頁面發起長輪詢請求/polling/watch/10086?監聽消息更變,請求被掛起,不變更數據直至超時,再次發起了長輪詢請求;緊接著手動變更數據/polling/publish/10086,長輪詢得到響應,前端處理業務邏輯完成后再次發起請求,如此循環往復。

長輪詢相比于短輪詢在性能上提升了很多,但依然會產生較多的請求,這是它的一點不完美的地方。

iframe流

iframe流就是在頁面中插入一個隱藏的

服務端直接組裝html、js腳本數據向response寫入就行了

@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>"); } }}

但我個人不推薦,因為它在瀏覽器上會顯示請求未加載完,圖標會不停旋轉,簡直是強迫癥殺手。

SSE (我的方式)

很多人可能不知道,服務端向客戶端推送消息,其實除了可以用WebSocket?這種耳熟能詳的機制外,還有一種服務器發送事件(Server-sent events?),簡稱SSE。

SSE?它是基于HTTP協議的,我們知道一般意義上的HTTP協議是無法做到服務端主動向客戶端推送消息的,但SSE是個例外,它變換了一種思路。

SSE在服務器和客戶端之間打開一個單向通道,服務端響應的不再是一次性的數據包而是text/event-stream類型的數據流信息,在有數據變更時從服務器流式傳輸到客戶端。

整體的實現思路有點類似于在線視頻播放,視頻流會連續不斷的推送到瀏覽器,你也可以理解成,客戶端在完成一次用時很長(網絡不暢)的下載。

SSE?與WebSocket作用相似,都可以建立服務端與瀏覽器之間的通信,實現服務端向客戶端推送消息,但還是有些許不同:

SSE 是基于HTTP協議的,它們不需要特殊的協議或服務器實現即可工作;WebSocket需單獨服務器來處理協議。SSE 單向通信,只能由服務端向客戶端單向通信;webSocket全雙工通信,即通信的雙方可以同時發送和接受信息。SSE 實現簡單開發成本低,無需引入其他組件;WebSocket傳輸數據需做二次解析,開發門檻高一些。SSE 默認支持斷線重連;WebSocket則需要自己實現。SSE 只能傳送文本消息,二進制數據需要經過編碼后傳送;WebSocket默認支持傳送二進制數據。SSE 與 WebSocket 該如何選擇?

技術并沒有好壞之分,只有哪個更合適

SSE好像一直不被大家所熟知,一部分原因是出現了WebSockets,這個提供了更豐富的協議來執行雙向、全雙工通信。對于游戲、即時通信以及需要雙向近乎實時更新的場景,擁有雙向通道更具吸引力。

但是,在某些情況下,不需要從客戶端發送數據。而你只需要一些服務器操作的更新。比如:站內信、未讀消息數、狀態更新、股票行情、監控數量等場景,SEE?不管是從實現的難易和成本上都更加有優勢。此外,SSE 具有WebSockets?在設計上缺乏的多種功能,例如:自動重新連接?、事件ID?和發送任意事件的能力。

前端只需進行一次HTTP請求,帶上唯一ID,打開事件流,監聽服務端推送的事件就可以了

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

服務端的實現更簡單,創建一個SseEmitter?對象放入sseEmitterMap進行管理

private static Map sseEmitterMap = new ConcurrentHashMap<>();/** * 創建連接 * * @date: 2022/7/12 14:51 * @auther: 公眾號:程序員小富 */public static SseEmitter connect(String userId) { try { // 設置超時時間,0表示不過期。默認30秒 SseEmitter sseEmitter = new SseEmitter(0L); // 注冊回調 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("創建新的sse連接異常,當前用戶:{}", userId); } return null;}/** * 給指定用戶發送消息 * * @date: 2022/7/12 14:51 * @auther: 公眾號:程序員小富 */public static void sendMessage(String userId, String message) { if (sseEmitterMap.containsKey(userId)) { try { sseEmitterMap.get(userId).send(message); } catch (IOException e) { log.error("用戶[{}]推送異常:{}", userId, e.getMessage()); removeUser(userId); } }}

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

注意:SSE不支持IE?瀏覽器,對其他主流瀏覽器兼容性做的還不錯。

MQTT

什么是 MQTT協議?

MQTT?全稱(Message Queue Telemetry Transport):一種基于發布/訂閱(publish?/subscribe?)模式的輕量級?通訊協議,通過訂閱相應的主題來獲取消息,是物聯網(Internet of Thing)中的一個標準傳輸協議。

該協議將消息的發布者(publisher?)與訂閱者(subscriber)進行分離,因此可以在不可靠的網絡環境中,為遠程連接的設備提供可靠的消息服務,使用方式與傳統的MQ有點類似。

TCP?協議位于傳輸層,MQTT?協議位于應用層,MQTT?協議構建于TCP/IP?協議上,也就是說只要支持TCP/IP?協議棧的地方,都可以使用MQTT協議。

為什么要用 MQTT協議?

MQTT?協議為什么在物聯網(IOT)中如此受偏愛?而不是其它協議,比如我們更為熟悉的HTTP協議呢?

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

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

MQTT協議的介紹

我也沒想到 springboot + rabbitmq 做智能家居,會這么簡單

MQTT實現消息推送

未讀消息(小紅點),前端 與 RabbitMQ 實時消息推送實踐,賊簡單~

Websocket

websocket應該是大家都比較熟悉的一種實現消息推送的方式,上邊我們在講SSE的時候也和websocket進行過比較。

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

圖片源于網絡

springboot整合websocket,先引入websocket相關的工具包,和SSE相比額外的開發成本。

org.springframework.boot spring-boot-starter-websocket

服務端使用@ServerEndpoint?注解標注當前類為一個websocket服務器,客戶端可以通過ws://localhost:7777/webSocket/10086來連接到WebSocket服務器端。

@Component@Slf4j@ServerEndpoint("/websocket/{userId}")public class WebSocketServer { //與某個客戶端的連接會話,需要通過它來給客戶端發送數據 private Session session; private static final CopyOnWriteArraySet webSockets = new CopyOnWriteArraySet<>(); // 用來存在線連接數 private static final Map sessionPool = new HashMap(); /** * 公眾號:程序員小富 * 鏈接成功調用的方法 */ @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消息: 有新的連接,總數為:" + webSockets.size()); } catch (Exception e) { } } /** * 公眾號:程序員小富 * 收到客戶端消息后調用的方法 */ @OnMessage public void onMessage(String message) { log.info("websocket消息: 收到客戶端消息:" + message); } /** * 公眾號:程序員小富 * 此為單點消息 */ public void sendOneMessage(String userId, String message) { Session session = sessionPool.get(userId); if (session != null && session.isOpen()) { try { log.info("websocket消: 單點消息:" + message); session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } }}

前端初始化打開WebSocket連接,并監聽連接狀態,接收服務端數據或向服務端發送數據。

<script> var ws = new WebSocket("ws://localhost:7777/webSocket/10086"); // 獲取連接狀態 console.log("ws連接狀態:" + ws.readyState); //監聽是否連接成功 ws.onopen = function () { console.log("ws連接狀態:" + ws.readyState); //連接成功則發送一個數據 ws.send("test1"); } // 接聽服務器發回的信息并處理展示 ws.onmessage = function (data) { console.log("接收到來自服務器的消息:"); console.log(data); //完成通信后關閉WebSocket連接 ws.close(); } // 監聽連接關閉事件 ws.onclose = function () { // 監聽整個過程中websocket的狀態 console.log("ws連接狀態:" + ws.readyState); } // 監聽并處理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>

頁面初始化建立websocket連接,之后就可以進行雙向通信了,效果還不錯

自定義推送

上邊我們給我出了6種方案的原理和代碼實現,但在實際業務開發過程中,不能盲目的直接拿過來用,還是要結合自身系統業務的特點和實際場景來選擇合適的方案。

推送最直接的方式就是使用第三推送平臺,畢竟錢能解決的需求都不是問題,無需復雜的開發運維,直接可以使用,省時、省力、省心,像goEasy、極光推送都是很不錯的三方服務商。

一般大型公司都有自研的消息推送平臺,像我們本次實現的web站內信只是平臺上的一個觸點而已,短信、郵件、微信公眾號、小程序凡是可以觸達到用戶的渠道都可以接入進來。

圖片來源于網絡

消息推送系統內部是相當復雜的,諸如消息內容的維護審核、圈定推送人群、觸達過濾攔截(推送的規則頻次、時段、數量、黑白名單、關鍵詞等等)、推送失敗補償非常多的模塊,技術上涉及到大數據量、高并發的場景也很多。所以我們今天的實現方式在這個龐大的系統面前只是小打小鬧。

Github地址

文中所提到的案例我都一一的做了實現,整理放在了Github上,覺得有用就Star一下吧!

傳送門:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-realtime-data

關鍵詞: 發送數據 可以使用 主動查詢 就可以了

上一篇:

下一篇:

X 關閉

X 關閉

久久国产精品一区二区三区四区,久色婷婷小香蕉久久,国产日韩欧美在线播放不卡,另类av一区二区
亚洲国产人成综合网站| 亚洲午夜女主播在线直播| 国产精品永久| 久久狠狠亚洲综合| 亚洲人成在线观看一区二区| 欧美一区午夜精品| 欧美阿v一级看视频| 男女av一区三区二区色多| 国产精品免费网站| 欧美伊人影院| 欧美日韩一区国产| 亚洲福利在线视频| 亚洲伊人网站| 激情综合色综合久久综合| 亚洲夜晚福利在线观看| 一本色道久久综合亚洲精品按摩| 欧美性事在线| 黄色资源网久久资源365| 99国产精品国产精品毛片| 国产精品免费一区二区三区观看| 亚洲国产精品热久久| 久久九九国产精品怡红院| 国产一区91精品张津瑜| 亚洲精品老司机| 久久久精品视频成人| 亚洲欧美国产视频| 午夜精品免费视频| 国产日韩欧美夫妻视频在线观看| 亚洲免费中文字幕| 亚洲欧洲精品一区二区| 欧美亚洲视频在线观看| 一本色道婷婷久久欧美| 亚洲欧美影音先锋| 国产亚洲欧美aaaa| 亚洲国产日韩在线一区模特| 野花国产精品入口| 久久久精品性| 美女视频一区免费观看| 国产精品视频久久| 亚洲电影在线| 在线看一区二区| 欧美性色aⅴ视频一区日韩精品| 狠狠综合久久| 香蕉精品999视频一区二区| 国产一在线精品一区在线观看| 欧美精品一区在线| 欧美成年人网| 国产区日韩欧美| 亚洲午夜精品一区二区| 欧美精品一区二区高清在线观看| 一本大道av伊人久久综合| 久久精品中文字幕免费mv| 黄色精品网站| 亚洲国产精品123| 欧美丝袜一区二区| 亚洲国产成人av在线| 欧美日韩在线视频一区| 国产麻豆日韩| 在线观看成人一级片| 欧美jizzhd精品欧美喷水| 亚洲黄色av| 亚洲在线成人| 日韩午夜激情av| 欧美另类综合| 久久久久久久综合| 欧美日韩成人一区二区三区| 欧美激情在线播放| 亚洲大片免费看| 精品粉嫩aⅴ一区二区三区四区| 国产精品激情电影| 久久精品人人做人人爽电影蜜月| 又紧又大又爽精品一区二区| 国产一区日韩二区欧美三区| 欧美综合第一页| 国产一区二区三区直播精品电影| 久久国产精品久久久久久| 夜夜嗨av一区二区三区网站四季av| 欧美三级电影精品| 久久精品国产综合| 久久乐国产精品| 伊人久久噜噜噜躁狠狠躁| 亚洲作爱视频| 亚洲区第一页| 宅男精品视频| 久久国产加勒比精品无码| 国产精品jizz在线观看美国| 久久五月婷婷丁香社区| 欧美日韩在线视频一区| 亚洲精品免费在线观看| 99综合精品| 老司机一区二区三区| 欧美三级视频在线播放| 欧美人与性动交α欧美精品济南到| 国产精品一区二区在线观看网站| 欧美日韩在线免费观看| 黄色亚洲大片免费在线观看| 亚洲精品视频在线播放| 欧美精品不卡| 一区在线影院| 亚洲电影视频在线| 国产亚洲二区| 亚洲一区二区三区中文字幕在线| 国产一区二区精品久久| 国产精品一区久久| 黄色成人免费网站| 午夜久久资源| 国产曰批免费观看久久久| 在线成人小视频| 国产亚洲美州欧州综合国| 欧美夫妇交换俱乐部在线观看| 一区二区欧美日韩| 亚洲永久在线| 欧美激情一区二区三区成人| 美女诱惑黄网站一区| 美日韩精品免费观看视频| 性色av一区二区三区在线观看| 在线播放豆国产99亚洲| 久久久一二三| 亚洲精选视频免费看| 国产亚洲激情| 国产有码一区二区| 老**午夜毛片一区二区三区| 亚洲一级片在线看| 国产精品青草综合久久久久99| 亚洲自拍都市欧美小说| 国产一区二区久久久| 国产精品视频久久久| 国产日韩一级二级三级| 亚洲特色特黄| 亚洲成人自拍视频| 欧美日本久久| 亚洲成人自拍视频| 欧美精品v日韩精品v韩国精品v| 欧美成在线视频| 今天的高清视频免费播放成人| 亚洲国产高清在线观看视频| 欧美日韩xxxxx| 国产精品久久久久aaaa| 免费h精品视频在线播放| 亚洲一区二区视频| 欧美日韩综合另类| 亚洲欧美日本视频在线观看| 一本色道久久综合| 国产精品久久久久久久第一福利| 欧美电影在线播放| 99视频精品全国免费| 狠狠色综合播放一区二区| 欧美成人性生活| 亚洲欧美国产日韩天堂区| 国内一区二区三区| 欧美乱妇高清无乱码| 欧美视频免费| 在线日本欧美| 精品91视频| 亚洲午夜激情免费视频| 欧美激情免费在线| 欧美激情视频一区二区三区在线播放| 一本色道综合亚洲| av成人免费在线| 欧美少妇一区| 中文一区二区| 欧美系列亚洲系列| 依依成人综合视频| 欧美特黄视频| 午夜精品在线视频| 一区二区三区在线观看国产| 麻豆精品精品国产自在97香蕉| 在线观看成人av| 国产日韩精品视频一区| 久久露脸国产精品| 亚洲成人自拍视频| 亚洲欧洲99久久| 又紧又大又爽精品一区二区| 欧美国产日本高清在线| 国产欧美大片| 欧美专区在线| 一本色道久久综合亚洲精品不卡| 久久综合色天天久久综合图片| 欧美顶级大胆免费视频| 欧美婷婷久久| 国产精品jvid在线观看蜜臀| 亚洲国产日韩欧美综合久久| 狠狠狠色丁香婷婷综合久久五月| 国产欧美日韩一区二区三区在线| 亚洲午夜在线| 国产午夜精品全部视频在线播放| 亚洲国产小视频在线观看| 欧美日韩一级大片网址| 欧美午夜免费| 蜜桃视频一区| 久久国产直播| 乱人伦精品视频在线观看| 国产农村妇女精品一区二区| 欧美在线1区| 欧美性理论片在线观看片免费| 国产免费成人在线视频| 麻豆成人小视频| 亚洲精品免费一二三区| 亚洲欧洲99久久| 久久精品免费播放|