Java 服務端消息推送的實現(xiàn)小結
前言:當構建實時消息推送功能時,選擇適合的方案對于開發(fā)高效的實時應用至關重要。消息的推送無非就推、拉兩種數(shù)據(jù)模型。本文將介紹四種常見的消息實時推送方案:短輪詢(拉)、長輪訓(拉)、SSE(Server-Sent Events)(推)和WebSocket(推),并以Spring Boot作為技術底座,展示如何在Java全棧開發(fā)中實現(xiàn)這些功能。
1. 短輪詢(Short Polling)
什么是短輪詢?
短輪詢是一種簡單的實時消息推送方案,其中客戶端通過定期向服務器發(fā)送請求來獲取最新的消息。服務器在接收到請求后立即響應,無論是否有新消息。如果服務器沒有新消息可用,客戶端將再次發(fā)送請求。
短輪詢的實現(xiàn)
在Spring Boot中,可以通過HTTP接口和定時任務來實現(xiàn)短輪詢。下面是一個簡單的示例:
// -- 后端接口
@GetMapping("/short")
public String getShort() {
long l = System.currentTimeMillis();
if((l&1) == 1) {
return "ok";
}
return "fail";
}
// --- 前端頁面
@org.springframework.stereotype.Controller
public class Controller {
@GetMapping("/s")
public String s() {
return "s";
}
}
// -- s.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>短輪詢</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<script>
function pollMessage() {
// 發(fā)送輪詢請求
const xhr = new XMLHttpRequest();
xhr.open("GET", "/short", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
document.getElementById("message").innerHTML = xhr.responseText;
}
}
};
xhr.send();
}
setInterval(()=>{
pollMessage()
}, 1000)
</script>
</body>
</html>在上述示例中,getShort()方法用于返回消息,而s方法用于渲染s.html。客戶端可以定期調用getShort()接口來獲取最新的消息。
短輪詢的特點與限制
短輪詢的實現(xiàn)簡單,但存在一些特點和限制:
- 高延遲: 客戶端需要定期發(fā)送請求,無論是否有新消息。這會導致一定的延遲,特別是在消息更新較慢的情況下。
- 高網(wǎng)絡負載: 客戶端需要頻繁發(fā)送請求,即使消息沒有更新。這會增加服務器和網(wǎng)絡的負載。
- 實時性差: 由于需要等待下一次輪詢才能獲取新消息,短輪詢的實時性相對較差。
2. 長輪詢(Long Polling)
什么是長輪詢?
長輪詢是改進的輪詢方法,它在沒有新消息時會保持請求掛起,直到有新消息到達或超時。相比于短輪詢,長輪詢可以更快地獲取新消息,減少了不必要的請求。
長輪詢的實現(xiàn)
在Spring Boot中,可以使用異步請求和定時任務來實現(xiàn)長輪詢。下面是一個簡單的示例:
// -- 請求接口
/**
* 長輪詢
* @return
*/
@GetMapping("/long")
public DeferredResult<String> getLong() {
DeferredResult<String> deferredResult = new DeferredResult<>();
if (latestMessage != null) {
deferredResult.setResult(latestMessage);
} else {
// 使用定時任務設置超時時間
TimerTask timeoutTask = new TimerTask() {
@Override
public void run() {
deferredResult.setResult(null);
}
};
Timer timer = new Timer();
timer.schedule(timeoutTask, 5000); // 設置超時時間為5秒
// 設置回調函數(shù),在消息到達時觸發(fā)
deferredResult.onTimeout(() -> {
timer.cancel();
deferredResult.setResult(null);
});
deferredResult.onCompletion(timer::cancel);
}
return deferredResult;
}
/**
* 設置消息
* @param message
*/
@PostMapping("/send-message")
public void sendMessage(@RequestBody String message) {
latestMessage = message;
}
// -- 前端請求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>長輪詢</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<p>請求次數(shù):<span id="cnt"></span></p>
<script>
var cnt = 0
function pollMessage() {
// 發(fā)送輪詢請求
const xhr = new XMLHttpRequest();
xhr.open("GET", "/long", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
document.getElementById("message").innerHTML = xhr.responseText;
}
}
};
xhr.send();
}
setInterval(()=>{
++cnt;
document.getElementById('cnt').innerHTML = cnt.toString()
pollMessage()
}, 5000)
</script>
</body>
</html>在上述示例中,getLong()方法返回一個DeferredResult對象,它會在有新消息到達時觸發(fā)回調函數(shù)。如果在超時時間內沒有新消息到達,DeferredResult對象將返回null。
長輪詢的特點與限制
長輪詢具有以下特點與限制:
- 減少請求次數(shù): 長輪詢可以更快地獲取新消息,相比于短輪詢,可以減少不必要的請求次數(shù)。
- 減少網(wǎng)絡負載: 當沒有新消息時,長輪詢會保持請求掛起,減少了頻繁的請求,從而減輕了服務器和網(wǎng)絡的負載。
- 相對實時性提升: 長輪詢可以更快地獲取新消息,相比于短輪詢,實時性有所提升。然而,仍然需要等待下一次輪詢才能獲取新消息。
3. SSE(Server-Sent Events)

什么是SSE?
當使用Server-Sent Events(SSE)時,客戶端(通常是瀏覽器)與服務器之間建立一種持久的連接,使服務器能夠主動向客戶端發(fā)送數(shù)據(jù)。這種單向的、服務器主動推送數(shù)據(jù)的通信模式使得實時更新的數(shù)據(jù)能夠被實時地傳送到客戶端,而無需客戶端進行輪詢請求。
SSE的工作原理如下:
- 建立連接:客戶端通過使用
EventSource對象在瀏覽器中創(chuàng)建一個與服務器的連接??蛻舳讼蚍掌靼l(fā)送一個HTTP請求,請求的頭部包含Accept: text/event-stream,以表明客戶端希望接收SSE數(shù)據(jù)。服務器響應這個請求,并建立一個持久的HTTP連接。 - 保持連接:服務器保持與客戶端的連接打開狀態(tài),不斷發(fā)送數(shù)據(jù)。這個連接是單向的,只允許服務器向客戶端發(fā)送數(shù)據(jù),客戶端不能向服務器發(fā)送數(shù)據(jù)。
- 服務器發(fā)送事件:服務器使用
Content-Type: text/event-stream標頭來指示響應是SSE數(shù)據(jù)流。服務器將數(shù)據(jù)封裝在特定的SSE格式中,每個事件都以data:開頭,后面是實際的數(shù)據(jù)內容,以及可選的其他字段,如event:和id:。服務器發(fā)送的數(shù)據(jù)可以是任何文本格式,通常是JSON。 - 客戶端接收事件:客戶端通過
EventSource對象監(jiān)聽服務器發(fā)送的事件。當服務器發(fā)送事件時,EventSource對象會觸發(fā)相應的事件處理程序,開發(fā)人員可以在處理程序中獲取到事件數(shù)據(jù)并進行相應的操作。常見的事件是message事件,表示接收到新的消息。 - 斷開連接:當客戶端不再需要接收服務器的事件時,可以關閉連接。客戶端可以調用
EventSource對象的close()方法來顯式關閉連接,或者瀏覽器在頁面卸載時會自動關閉連接。
SSE的實現(xiàn)
在Spring Boot中,可以使用SseEmitter類來實現(xiàn)SSE。下面是一個簡單的示例:
@RestController
public class SSEController {
private SseEmitter sseEmitter;
@GetMapping("/subscribe")
public SseEmitter subscribe() {
sseEmitter = new SseEmitter();
return sseEmitter;
}
@PostMapping("/send-message")
public void sendMessage(@RequestBody String message) {
try {
if (sseEmitter != null) {
sseEmitter.send(SseEmitter.event().data(message));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// - s.html
<!DOCTYPE html>
<html>
<head>
<title>SSE Demo</title>
</head>
<body>
<h1>SSE Demo</h1>
<div id="message-container"></div>
<script>
// 創(chuàng)建一個EventSource對象,指定SSE的服務端端點
var eventSource = new EventSource('/subscribe');
console.log("eventSource=", eventSource)
// 監(jiān)聽message事件,接收從服務端發(fā)送的消息
eventSource.addEventListener('message', function(event) {
var message = event.data;
console.log("message=", message)
var messageContainer = document.getElementById('message-container');
messageContainer.innerHTML += '<p>' + message + '</p>';
});
</script>
</body>
</html>在上述示例中,客戶端可以通過訪問/subscribe接口來訂閱SSE事件,服務器會返回一個SseEmitter對象。當有新消息到達時,調用SseEmitter對象的send()方法發(fā)送消息。

SSE的特點與限制
SSE具有以下特點與限制:
- 實時性較好: SSE使用了持久連接,可以實現(xiàn)比短輪詢和長輪詢更好的實時性。
- 單向通信: SSE是單向的,只允許服務器向客戶端推送消息,客戶端無法向服務器發(fā)送消息。
- 不適用于低版本瀏覽器: SSE是HTML5的一部分,不支持低版本的瀏覽器。在使用SSE時,需要確保客戶端瀏覽器的兼容性。
4. WebSocket

什么是WebSocket?
WebSocket是一種雙向通信協(xié)議,允許在單個持久連接上進行全雙工通信。與之前介紹的方案不同,WebSocket提供了雙向通信的能力,可以實現(xiàn)實時的雙向數(shù)據(jù)傳輸。
WebSocket的實現(xiàn)
在Spring Boot中,可以使用Spring WebSocket模塊來實現(xiàn)WebSocket功能。下面是一個簡單的示例:
1. 創(chuàng)建一個WebSocket處理器:
@Component
public class WebSocketHandler extends TextWebSocketHandler {
private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (WebSocketSession webSocketSession : sessions) {
webSocketSession.sendMessage(message);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}2. 配置WebSocket端點:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/websocket").setAllowedOrigins("*");
}
}在上述示例中,WebSocketHandler處理器負責處理WebSocket連接、消息傳遞和連接關閉等事件。WebSocketConfig類用于配置WebSocket端點。
3. 前端實現(xiàn)
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Demo</title>
</head>
<body>
<h1>WebSocket Demo</h1>
<div id="message-container"></div>
<script>
// 創(chuàng)建WebSocket對象,并指定服務器的URL
var socket = new WebSocket('ws://localhost:8080/websocket');
// 監(jiān)聽WebSocket的連接事件
socket.onopen = function(event) {
console.log('WebSocket connected');
};
// 監(jiān)聽WebSocket的消息事件
socket.onmessage = function(event) {
var message = event.data;
var messageContainer = document.getElementById('message-container');
messageContainer.innerHTML += '<p>' + message + '</p>';
};
// 監(jiān)聽WebSocket的關閉事件
socket.onclose = function(event) {
console.log('WebSocket closed');
};
// 發(fā)送消息到服務器
function sendMessage() {
var messageInput = document.getElementById('message-input');
var message = messageInput.value;
socket.send(message);
messageInput.value = '';
}
</script>
<input type="text" id="message-input" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
</body>
</html>
WebSocket的特點與限制
WebSocket具有以下特點與限制:
- 實時性最佳: WebSocket 提供了真正的雙向通信,可以實現(xiàn)實時的雙向數(shù)據(jù)傳輸,具有最佳的實時性。
- 低延遲: 與輪詢和長輪詢相比,WebSocket 使用單個持久連接,減少了連接建立和斷開的開銷,從而降低了延遲。
- 雙向通信: WebSocket 允許服務器與客戶端之間進行雙向通信,服務器可以主動向客戶端發(fā)送消息,同時客戶端也可以向服務器發(fā)送消息。
- 較高的網(wǎng)絡負載: WebSocket 使用長連接,會占用一定的網(wǎng)絡資源。在大規(guī)模并發(fā)場景下,需要注意服務器的負載情況。
- 瀏覽器支持: 大多數(shù)現(xiàn)代瀏覽器都支持 WebSocket,但需要注意在開發(fā)過程中考慮不同瀏覽器的兼容性。
總結
本文介紹了四種常見的消息實時推送方案:短輪詢、長輪詢、SSE 和 WebSocket,并以 Spring Boot 作為技術底座,展示了如何在 Java 全棧開發(fā)中實現(xiàn)這些功能。
- 短輪詢是一種簡單的實時消息推送方案,但存在高延遲、高網(wǎng)絡負載和實時性差的限制。
- 長輪詢通過保持請求掛起來減少不必要的請求次數(shù),提高了實時性,但仍需要輪詢才能獲取新消息。
- SSE 使用持久連接實現(xiàn)單向實時消息推送,具有較好的實時性,但只支持服務器向客戶端的單向通信。
- WebSocket 提供了真正的雙向通信,具有最佳的實時性和低延遲,但需要注意較高的網(wǎng)絡負載和瀏覽器兼容性。
選擇合適的消息實時推送方案取決于具體的需求和場景。根據(jù)應用程序的要求和預期的用戶體驗,開發(fā)人員可以選擇適當?shù)姆桨竵韺崿F(xiàn)實時消息推送功能。
注意
以上實現(xiàn)均屬于demo 級別,為了簡單演示,將所有的服務保證措施都刪除了,所以存在包括但不限于以下缺點
比如
sse
- 沒有做會話管理
- 明文傳輸
WebSocket
客戶端連接的區(qū)分,目前的實現(xiàn)屬于消息廣播。
連接的可靠性保證:心跳檢測以及自動重連等。
消息的明文傳輸
到此這篇關于Java 服務端消息推送的實現(xiàn)小結的文章就介紹到這了,更多相關Java 服務端消息推送內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java.lang.OutOfMemoryError 錯誤整理及解決辦法
這篇文章主要介紹了java.lang.OutOfMemoryError 錯誤整理及解決辦法的相關資料,需要的朋友可以參考下2016-10-10
Java生成PDF文檔兩個超實用的庫( iText和Apache PDFBox)
這篇文章主要介紹了Java生成PDF文檔兩個超實用的庫,分別是用 iText庫以及用Apache PDFBox庫生成PDF,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-02-02
利用java讀取web項目中json文件為map集合方法示例
這篇文章主要給大家介紹了關于利用java讀取web項目中json文件為map集合的相關資料,文中通過示例代碼給大家介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-08-08
如何使用jakarta.json進行json序列化和反序列化
java里,json框架何其多,常見的有jackson、fastjson、gson等,本文重點介紹如何使用jakarta.json進行json序列化和反序列化,需要的朋友可以參考下,2024-07-07

