前言
系統的業務中,網頁需要彈窗"報警信息"。前端獲取數據的方式通過輪詢調后端接口。也考慮過WebSocket 的方式,但好像兼容性不太好。現在發現還有其他更優的方式,在此記錄一下。
網頁端收服務端的消息的方式
一、輪詢拉取
客戶端間隔的發送ajax請求服務器的數據。
- 優點:實現簡單
- 缺點:
- 實時性比較差,最大時差就是前端的間隔
- 效率低:如果長時間沒有消息,客戶端還是在輪詢請求。浪費服務端的資源
二、建立長連接
- 如果要兼顧實時性和效率,長連接是最佳之選,PC端聊天軟件基本都是使用長連接。網頁端常見的實現長連接的方式有兩種:WebSocket和FlashSocket
- 缺點:兼容性差,有些瀏覽器不支持
三、Http長輪詢 (通用方式)
通過HTTP短連接拼裝長連接,具體是通過“夯住”“只收推送通知”的“通知連接”來實現的,能夠做到消息的實時性到達。
客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息后再向服務器發送新的請求
在瀏覽器和服務端建立了一條"通知連接"的特點:
- 這是一條browser發往web-server的HTTP連接
- 這條連接只用來收取推送通知
- 不像普通的“請求-響應”式HTTP請求,這個HTTP會被服務端夯住,直到有推送通知到達,或者超過約定的時間
對於HTTP請求,為了提高效率,一般來說browser和web-server都會有一些設置,如果一條HTTP請求長時間沒有數據(例如,150秒),會被斷開。“通知連接”為了不被browser和web-server粗暴斷開,一般會設置一個約定閾值(例如,小於150秒),由系統返回一個空消息,以便“優雅返回”。
瀏覽器發起通知連接時,如果服務端的隊列里面有消息,則服務端立刻把消息返回。如果這條連接達到了"約定閾值"要斷開時,服務端返回空消息維持連接。當服務端返回消息(空的或者真實消息)的同時,瀏覽器會瞬間再發起連接。在消息過去和瀏覽器返回的時間差時有消息時,服務端將新的消息放入隊列(發生概率很小)
- 缺點:服務器hold連接會消耗資源
實現Http長輪詢
使用 DeferredResult 異步請求處理
@GetMapping("/async-deferredresult")
public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
LOG.info("Received async-deferredresult request");
DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000); // 模擬延遲六秒返回
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet thread freed");
return output;
}
// 設置超時,即在后端一致沒有數據的情況下,多長時間斷開連接。使用的是onTimeout()方法。
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout occurred.")));
// 如果后端出現了錯誤,可以設置onError()方法修改返回狀態碼:
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
})