摘要:本文介紹消息推送的三種常見方式:輪詢(定時請求,易增負擔)與長輪詢(阻塞請求至有數據 / 超時,減少請求)、SSE(HTTP 單向實時傳輸,純文本、自動重連)、WebSocket(全雙工通信,含客戶端 API 與 Java 服務端 Endpoint 實現)。
1. 消息推送常見方式
1.1 輪詢方式
輪詢
- 工作機制:瀏覽器按照指定的時間間隔,不斷向服務器發送 HTTP 請求(如示例中的?
GET/poll
)。服務器收到請求后,會實時返回數據給瀏覽器。 - 特點:不管服務器有沒有新數據,瀏覽器都會定時發請求。這種方式可能導致請求過于頻繁,即便服務器無數據更新,也會產生大量請求,增加服務器和網絡的負擔,而且如果輪詢間隔設置不合理,還可能出現數據更新的延遲(如圖中 “延遲” 所示)。
長輪詢
- 工作機制:瀏覽器發送 Ajax 請求(如示例中的?
GET/lpoll
)后,服務器接收到請求會 “阻塞” 該請求。也就是說,服務器不會立即返回響應,而是等待有數據更新或者請求超時的時候,才會把數據返回給瀏覽器。 - 特點:相比輪詢,長輪詢減少了不必要的請求次數。只有當有數據變化或者超時,服務器才會響應,能在一定程度上降低服務器和網絡的壓力,也能更及時地獲取數據更新(只要數據更新在超時前發生)。
簡單來說,輪詢是 “定時問”,長輪詢是 “問了等,有了才回”,二者在請求頻率、資源消耗和數據實時性等方面存在差異,可根據實際場景選擇使用。
1.2 SSE 技術
服務器發送事件(Server-Sent Events)是一種用于從服務器到客戶端的 單向、實時 數據傳輸技術,基于 HTTP協議實現。
它有幾個重要的特點:
- 單向通信:SSE 只支持服務器向客戶端的單向通信,客戶端不能向服務器發送數據。
- 文本格式:SSE 使用?純文本格式?傳輸數據,使用 HTTP 響應的?
text/event-stream
?MIME 類型。(數據流信息)- 保持連接:SSE 通過保持一個持久的 HTTP 連接,實現服務器向客戶端推送更新,而不需要客戶端頻繁輪詢。
- 自動重連:如果連接中斷,瀏覽器會自動嘗試重新連接,確保數據流的連續性。
SSE 數據格式
SSE 數據流的格式非常簡單,使用?event
?指定事件名稱,用于區分不同類型的消息。每個事件使用 data
字段,作為消息主體,事件以兩個換行符結束。還可以使用 id
字段來標識事件,并且 retry
字段可以設置重新連接的時間間隔。
event: 事件名\n // 可選,用于區分不同類型的消息
data: 消息內容\n // 必選,消息主體(可多行,每行以 data: 開頭)
id: 消息ID\n // 可選,用于客戶端記錄最后接收的消息ID(重連時可通過 Last-Event-ID 頭傳遞)
retry: 重連時間(毫秒)\n // 可選,指定客戶端重連間隔
\n // 空行表示一條消息結束
示例格式如下:
data: Third message\n
id: 3\n
\n
retry: 10000\n
data: Fourth message\n
\n
1.3 WebSocket
WebSocket 是一種網絡通信協議,它通過在單個、長期的連接上提供全雙工(雙向)的通信通道,來解決 HTTP 協議在實時通信方面的不足。
1.3.1 原理解析
這張圖解析了 WebSocket 連接的建立過程及原理,可分為以下幾個部分:
連接建立階段(基于 HTTP 協議升級)
- 客戶端請求:客戶端向服務器發送一個特殊的 HTTP 請求,請求中包含?
Upgrade: websocket
?等頭信息,表明希望將連接從 HTTP 協議升級為 WebSocket 協議。下方 “請求數據” 框里展示了具體的請求內容,像?GET ws://localhost/chat HTTP/1.1
(指定 WebSocket 連接的地址)、Connection: Upgrade
(表示要升級連接)、Upgrade: websocket
(明確升級到 WebSocket 協議)等。- 服務器響應:服務器收到請求后,返回?
HTTP/1.1 101 Switching Protocols
?響應,確認協議升級。下方 “響應數據” 框里呈現了響應的具體內容,包含?Upgrade: websocket
、Connection: Upgrade
?等,表明已切換到 WebSocket 協議。數據傳輸階段(基于 WebSocket 協議)
當協議升級完成后,客戶端和服務器就可以基于 WebSocket 協議進行全雙工通信了,雙方能相互主動發送數據(圖中 “發送數據” 的雙向箭頭體現了這一點),不再受 HTTP 協議請求 - 響應模式的限制,適合實時性要求高的場景,比如在線聊天、實時數據推送等。
1.3.2 客戶端API
1. WebSocket 對象創建
代碼示例:let ws = new WebSocket(URL);
作用:在瀏覽器中創建一個 WebSocket 實例,用于和服務器建立 WebSocket 連接。
URL 說明:
- 格式為?
協議://ip地址/訪問路徑
。 - 其中協議名稱為?
ws
(若為加密連接則用?wss
,類似 HTTPS),ip地址
?是服務器的地址,訪問路徑
?是 WebSocket 服務在服務器上的具體路徑。
2. WebSocket 對象相關事件
這部分通過表格列出了 WebSocket 實例常用的事件及對應的事件處理程序和描述:
事件 | 事件處理程序 | 描述 |
---|---|---|
open | ws.onopen | 當客戶端與服務器成功建立 WebSocket 連接時觸發該事件。 |
message | ws.onmessage | 當客戶端接收到服務器發送的數據時觸發該事件。 |
close | ws.onclose | 當 WebSocket 連接關閉時(無論是客戶端主動關閉,還是服務器關閉,或者連接異常斷開)觸發該事件。 |
3. WebSocket 對象提供的方法
這部分介紹了 WebSocket 實例的方法:
方法名稱 | 描述 |
---|---|
send() | 客戶端通過調用 WebSocket 實例的?send() ?方法,向服務器發送數據。 |
1.3.3 代碼實現
<script>let ws = new WebSocket("ws://localhost/chat");ws.onopen = function() {};ws.onmessage = function(evt) {// 通過 evt.data 可以獲取服務器發送的數據};ws.onclose = function() {};
</script>
1. 創建 WebSocket 實例
let ws = new WebSocket("ws://localhost/chat");
- 使用?
new WebSocket()
?構造函數創建一個 WebSocket 連接對象。 - 參數?
"ws://localhost/chat"
?是 WebSocket 服務器的地址,ws://
?表示使用 WebSocket 協議(如果是加密的 WebSocket 連接,使用?wss://
),localhost
?是本地服務器地址,/chat
?是具體的 WebSocket 端點路徑,用于標識要連接的服務。
2. 處理連接建立事件(onopen
)
ws.onopen = function() {// 連接成功建立后執行的代碼
};
onopen
?是 WebSocket 對象的一個事件處理屬性。- 當客戶端與 WebSocket 服務器成功建立連接時,這個函數會被調用。通常可以在這里執行一些連接成功后的操作,比如向服務器發送初始化消息等。
3. 處理消息接收事件(onmessage
)
ws.onmessage = function(evt) {// 通過 evt.data 可以獲取服務器發送的數據
};
onmessage
?事件在客戶端接收到服務器發送的數據時觸發。- 回調函數的參數?
evt
?包含了服務器發送的數據,通過?evt.data
?可以獲取到具體的數據內容,數據可以是字符串、Blob 或者 ArrayBuffer 等格式,這里通常需要根據實際情況對數據進行解析和處理,比如展示聊天消息、更新實時數據等。
4. 處理連接關閉事件(onclose
)
ws.onclose = function() {// 連接關閉后執行的代碼
};
onclose
?事件在 WebSocket 連接關閉時觸發,不管是客戶端主動關閉還是服務器關閉連接,或者連接異常斷開,這個函數都會被調用。可以在這里進行一些資源清理、提示用戶連接已關閉或者嘗試重新連接等操作。
1.3.4 服務端API
Java WebSocket 應用由一系列的?Endpoint
?組成。Endpoint
?是一個 Java 對象,代表 WebSocket 鏈接的一端,對于服務端,我們可以視為處理具體 WebSocket 消息的接口。
Endpoint 的定義方式
我們可以通過兩種方式定義?Endpoint
:
- 第一種是編程式,即繼承類?
javax.websocket.Endpoint
?并實現其方法。 - 第二種是注解式,即定義一個 POJO,并添加?
@ServerEndpoint
?相關注解。
Endpoint 的生命周期
Endpoint
?實例在 WebSocket 握手時創建,并在客戶端與服務端鏈接過程中有效,最后在鏈接關閉時結束。在?Endpoint
?接口中明確定義了與其生命周期相關的方法,規范實現者確保生命周期的各個階段調用實例的相關方法。生命周期方法如下:
方法 | 描述 | 注解 |
---|---|---|
onOpen() | 當開啟一個新的會話時調用,該方法是客戶端與服務端握手成功后調用的方法 | @OnOpen |
onClose() | 當會話關閉時調用 | @OnClose |
onError() | 當連接過程異常時調用 | @OnError |
1.3.5 服務端接收和推送數據
1.3.6 代碼實現
@ServerEndpoint("/chat")
@Component
public class ChatEndpoint {@OnOpen//連接建立時被調用public void onOpen(Session session, EndpointConfig config) {}@OnMessage//接收到客戶端發送的數據時被調用public void onMessage(String message) {}@OnClose//連接關閉時被調用public void onClose(Session session) {}
}
大功告成!