后端向前端推送消息的常見方式有 websocket、輪詢等方式。還有一種方式:Server-Sent Event (簡稱SSE)。
SSE本質
嚴格說,HTTP 協議無法做到服務器主動推送消息。但是,有一種變通方法,就是服務器向客戶端聲明,接下來要發送的是流消息(streaming)。
也就是說,發送的不是一次性的數據包,而是一個數據流,會連續不斷地發送過來。這時,客戶端不會關閉連接,會一直等着服務器發送新的數據流,視頻播放就是這樣的例子。本質上,這種通信就是以流信息的方式,完成一次用時很長的下載。
SSE 就是利用這種機制,使用流消息向瀏覽器推送信息,它基於 HTTP 協議。
SSE特點
SSE 與 WebSocket 作用相似,都是建立瀏覽器與服務器之間的通信渠道,然后服務器向瀏覽器推送消息。
SSE 是單向通道,只能服務器向瀏覽器發送,因為流信息本質上就是下載。
SSE的優點如下:
- SSE 使用 HTTP 協議;WebSocket 是一個獨立協議。
- SSE 屬於輕量級,使用簡單;WebSocket 協議相對復雜。
- SSE 默認支持斷線重試;WebSocket 需要自己實現。
- SSE 一般只用來傳送文本,二進制數據需要編碼后傳送;WebSocket 默認支持傳送二進制數據。
- SSE 支持自定義發送的消息類型。
客戶端
SSE客戶端 API 部署在 EventSource 對象上。
// SSE 的客戶端 API 部署在 EventSource 對象上 if ('EventSource' in window) { // 瀏覽器支持 SSE } // 使用 SSE 時,瀏覽器首先生成一個 EventSource 實例,向服務器發起連接 var url = "http:127.0.0.1:8080/subscribe"; /* 上面的 url 可以與當前網址同域,也可以跨域。 * 跨域時,可以指定第二個參數,打開 withCredentials 屬性,表示是否一起發送cookie * 示例 var source = new EventSource(url, { withCredentials: true }); */ var source = new EventSource(url); // 連接一旦建立,就會觸發 open 事件,可以在 onopen 屬性定義回調函數 source.onopen = function (event) { // ... }; // 客戶端收到服務器發來的數據,就會觸發 message 事件,可以在 onmessage 屬性定義回調函數 source.onmessage = function (event) { // data 就是服務器傳回的數據(文本格式) var data = event.data; // handle message }; // 如果發生通信錯誤(比如連接中斷),就會觸發 error 事件,可以在 onerror 屬性定義回調函數 source.onerror = function (event) { // handle error event }; // 其中 close 方法用於關閉 SSE 連接 // source.close();
上述代碼示例了 EventSource 建立監理,監聽消息,異常等情況的處理方式。
默認情況下,服務器發來數據時,總是觸發 EventSource 實例 message 事件。開發者還可以自定義 SSE 事件,這種情況下,發送回來的數據不會觸發 message 事件。
source.addEventListener('foo', function (event) { var data = event.data; // handle message }, false);
上面是自定義事件的實例代碼。
服務端
服務端用 Java 編寫,采用 SpringBoot 框架,其中 SpringBoot 采用內置的 SseEmitter 對象支持 SSE。
@GetMapping("/subscribe") public SseEmitter subscribe() { // 設置超時時間為5分鍾 SseEmitter sseEmitter = new SseEmitter(5*60*1000L); // 直接返回 SseEmitter 對象就可以和客戶端連接 return sseEmitter; }
客戶端請求上面的接口之后就可以建立連接。如果要往客戶端發送消息,只需要調用 SseEmitter 實例的 send 方法。
// 調用 SseEmitter 的 send 方法即可往客戶端發送消息 try { sseEmitter.send("hello"); } catch (IOException e) { e.printStackTrace(); }
實際運用時,可以將用戶 id 和 SseEmitter 實例存在 Map 中,當需要給某個用戶推送消息時,先通過用戶 id 查找到其對應的 SseEmitter 實例,然后調用 send 方法,即可以給對應的用戶推送消息。
如果服務端要斷開連接,調用 SseEmitter 的 complete 方法即可。
小結
客戶端 js 的代碼中核心類是 EventSource,其包括建立連接、接收消息、異常處理時觸發的事件。客戶端 SpringBoot 框架中核心類是 SseEmitter 其中記錄連接的信息,調用其 send 方法就可以向客戶端推送消息。
參考:
本文內容大多參考以下文章,非原創。
http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html
https://www.cnblogs.com/yihuihui/p/12622729.html