SSE(Server-Sent Events)
是 HTML5 规范中的一种技术,允许服务器通过 HTTP 协议主动向客户端(如浏览器)推送数据。它基于单向通信模型(服务器 -> 客户端),适用于需要服务器实时更新数据的场景(如新闻推送、实时监控等)。
特性
基于 HTTP
:使用标准的 HTTP 协议,无需额外协议或端口
长连接
:客户端与服务器保持长连接,服务器可多次发送数据
文本数据格式
:默认支持 UTF-8 文本(如 JSON、纯文本),不支持二进制数据
自动重连
:内置断线重连机制,客户端会自动尝试重新连接
简单 API
:浏览器通过 EventSource
对象实现,代码简洁
单向通信
:仅支持服务器向客户端推送数据,客户端无法向服务器发送数据
SSE 与 WebSocket 的区别
特性 |
SSE |
WebSocket |
协议 |
基于 HTTP(长连接) |
独立协议(ws:// 或 wss:// ) |
通信方向 |
单向(服务器 -> 客户端) |
双向(全双工,客户端 ↔ 服务器) |
连接建立 |
普通 HTTP 请求,保持长连接 |
通过 HTTP 协议升级握手建立连接 |
数据格式 |
仅文本(UTF-8) |
支持文本和二进制数据 |
浏览器兼容性 |
不支持 IE/Edge(旧版) |
现代浏览器广泛支持 |
自动重连 |
内置支持 |
需手动实现 |
适用场景 |
服务器主动推送(如通知、实时数据流) |
双向实时交互(如聊天、游戏、协作编辑) |
复杂度 |
实现简单 |
需要处理双向通信和协议细节 |
如何选择 SSE 或 WebSocket?
-
SSE 适用场景
:更适合简单的服务器推送场景,依赖 HTTP 且实现成本低
- 只需服务器单向推送数据(如股票行情、新闻更新、日志流)。
- 希望利用 HTTP 生态(如身份验证、缓存、代理兼容性)。
- 需要快速实现且无需复杂交互。
-
WebSocket 适用场景
:适合需要双向交互和复杂实时功能的场景,但需处理更多协议细节
- 需要双向实时通信(如在线聊天、多人游戏、协同编辑)。
- 需要传输二进制数据(如音视频流)。
- 对低延迟要求极高。
根据需求选择合适的技术,必要时两者也可结合使用(如用 WebSocket 处理双向交互,SSE 处理单向推送)。
示例
SSE 服务端
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/events", sseHandler)
http.Handle("/", http.FileServer(http.Dir("./static"))) // 静态文件服务
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func sseHandler(w http.ResponseWriter, r *http.Request) {
// 设置 SSE 头部
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Access-Control-Allow-Origin", "*")
// 获取 flusher
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
// 发送事件
var id int
for {
select {
case <-r.Context().Done():
log.Println("Client disconnected")
return
default:
// 构造 SSE 格式数据
event := fmt.Sprintf("id: %d\nevent: message\ndata: %s\n\n",
id,
time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprint(w, event)
flusher.Flush()
id++
time.Sleep(1 * time.Second)
}
}
}
SSE 客户端
<!DOCTYPE html>
<html>
<head>
<title>SSE Client</title>
</head>
<body>
<div id="messages"></div>
<script>
const eventSource = new EventSource('http://localhost:8080/events');
eventSource.onmessage = function(e) {
const div = document.createElement('div');
div.textContent = `Received: ${e.data} (ID: ${e.lastEventId})`;
document.getElementById('messages').appendChild(div);
};
eventSource.onerror = function(err) {
console.error("EventSource failed:", err);
eventSource.close();
};
</script>
</body>
</html>
运行:go run main.go
,然后访问 http://localhost:8080
支持重试的客户端实现:
let eventSource = null;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
const INITIAL_RETRY_DELAY = 1000; // 初始重试延迟 1 秒
const MAX_RETRY_DELAY = 30000; // 最大重试延迟 30 秒
function connect() {
// 网络状态检测
if (!navigator.onLine) {
console.log('网络未连接,暂停重试');
window.addEventListener('online', connect);
return;
}
// 关闭现有连接(如果存在)
if (eventSource) {
eventSource.close();
}
eventSource = new EventSource('https://your-api-endpoint');
// 成功连接时重置计数器
eventSource.onopen = () => {
reconnectAttempts = 0;
console.log('SSE 连接成功');
};
// 消息处理
eventSource.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 错误处理
eventSource.onerror = () => {
if (eventSource.readyState === EventSource.CLOSED) {
console.error('SSE 连接异常关闭');
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
const delay = Math.min(
INITIAL_RETRY_DELAY * Math.pow(2, reconnectAttempts),
MAX_RETRY_DELAY
);
reconnectAttempts++;
console.log(`将在 ${delay}ms 后尝试第 ${reconnectAttempts} 次重连`);
setTimeout(connect, delay);
} else {
console.error('已达最大重试次数,停止重连');
}
}
};
}
// 初始化连接
connect();