當需要「服務端 → 客戶端」單向實時推送(股價、通知、日誌流、進度條)時,SSE 是比 WebSocket 更輕量、更標準、自帶斷線重連的原生方案。本文完整演示:前端 EventSource API、後端 Servlet 實現、關鍵協議細節、生產級優化。
| 方案 | 連接模式 | 雙向 | 自動重連 | 二進制 | 適用場景 |
|---|---|---|---|---|---|
| Short Polling | 短連接輪詢 | ✅ | ❌ | ✅ | 低實時性、簡單場景 |
| Long Polling | 長連接掛起 | ✅ | ❌ | ✅ | 中實時性、兼容舊瀏覽器 |
| SSE | 長連接流式 | ❌ (單向) | ✅ 原生 | ❌ (文本) | 服務端推送、日誌流、通知 |
| WebSocket | 全雙工長連接 | ✅ | ❌ (需自行實現) | ✅ | 雙向交互、IM、遊戲、協作編輯 |
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SSE 實時時間演示</title>
</head>
<body>
Time: <span id="foo"></span><br><br>
<button onclick="start()">Start</button>
<script>
function start() {
// 1. 建立連接
var eventSource = new EventSource("HelloServlet");
// 2. 接收消息(文本數據)
eventSource.onmessage = function(event) {
document.getElementById("foo").innerHTML = event.data;
};
// 3. 錯誤處理(自動重連中斷時觸發)
eventSource.onerror = function(err) {
console.log("SSE error, browser will auto-reconnect", err);
};
// 4. 自定義事件類型(可選)
// eventSource.addEventListener('customEvent', function(e) { ... });
}
</script>
</body>
</html>
data: ...\n\n,不支援二進制Access-Control-Allow-Origin@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 關鍵響應頭:聲明 SSE 內容類型
resp.setContentType("text/event-stream");
resp.setCharacterEncoding("UTF-8");
// 2. 禁用緩衝,確保實時發送
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Connection", "keep-alive");
// 3. 跨域支援(按需)
resp.setHeader("Access-Control-Allow-Origin", "*");
PrintWriter writer = resp.getWriter();
try {
for (int i = 0; i < 10; i++) {
// 4. SSE 協議格式:data: 內容\n\n(雙換行符結束)
String data = "data: " + System.currentTimeMillis() + "\n\n";
writer.write(data);
writer.flush(); // 關鍵:強制刷新到網路
Thread.sleep(1000); // 模擬業務間隔
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writer.close();
}
}
}
data: 第一行消息內容
data: 第二行消息內容(多行用多個 data:)
\n\n ← 空行分隔事件
event: customEvent ← 自定義事件類型(可選)
data: {"key": "value"} ← JSON 數據
id: 12345 ← 事件 ID,重連時瀏覽器發送 Last-Event-ID
\n\n
關鍵規則:
\n\n 結束data: 可多行,會拼接為單字符串(去掉末尾換行)id: 允許斷點續傳,重連時瀏覽器發送 Last-Event-ID 請求頭retry: 可指定重連間隔毫秒數| 優化點 | 方案 |
|---|---|
| 連接併發限制 | 瀏覽器對同域名限制 6 個 SSE 連接(HTTP/1.1),HTTP/2 可突破 |
| 心跳保活 | 服務端每 15-30 秒發送 :\n\n (註釋行) 防代理斷開 |
| 身份認證 | 握手階段攜帶 Token 參數,或 Cookie 自動攜帶 |
| 負載均衡 | Sticky Session 或 Redis Pub/Sub 廣播消息到所有實例 |
| 壓力測試 | 單實例支撐 ~10k 連接(依賴 Servlet 3.1 異步 Servlet) |
| 異步 Servlet 支撐高併發 | 使用 req.startAsync() 釋放容器線程 |
@WebServlet(urlPatterns = "/AsyncSSE", asyncSupported = true)
public class AsyncSSEServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/event-stream");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Connection", "keep-alive");
final AsyncContext async = req.startAsync();
async.setTimeout(0); // 無超時
async.start(() -> {
try (PrintWriter writer = resp.getWriter()) {
for (int i = 0; i < 100; i++) {
writer.write("data: " + System.currentTimeMillis() + "\n\n");
writer.flush();
Thread.sleep(1000);
}
} catch (Exception e) {
log.error("SSE error", e);
} finally {
async.complete();
}
});
}
}
SSE 以「原生自動重連、協議極簡、單向推送」三大優勢,成為服務端→客戶端實時流場景的最佳選擇。掌握 text/event-stream、data: ...\n\n、EventSource 三要素,配合異步 Servlet 與心跳保活,即可構建生產級推送服務。