SSE 介绍

发布时间: 更新时间: 总字数:1255 阅读时间:3m 作者: IP上海 分享 网址

SSE(Server-Sent Events) 是 HTML5 规范中的一种技术,允许服务器通过 HTTP 协议主动向客户端(如浏览器)推送数据。它基于单向通信模型(服务器 -> 客户端),适用于需要服务器实时更新数据的场景(如新闻推送、实时监控等)。

特性

  1. 基于 HTTP:使用标准的 HTTP 协议,无需额外协议或端口
  2. 长连接:客户端与服务器保持长连接,服务器可多次发送数据
  3. 文本数据格式:默认支持 UTF-8 文本(如 JSON、纯文本),不支持二进制数据
  4. 自动重连:内置断线重连机制,客户端会自动尝试重新连接
  5. 简单 API:浏览器通过 EventSource 对象实现,代码简洁
  6. 单向通信:仅支持服务器向客户端推送数据,客户端无法向服务器发送数据

SSE 与 WebSocket 的区别

特性 SSE WebSocket
协议 基于 HTTP(长连接) 独立协议(ws://wss://
通信方向 单向(服务器 -> 客户端) 双向(全双工,客户端 ↔ 服务器)
连接建立 普通 HTTP 请求,保持长连接 通过 HTTP 协议升级握手建立连接
数据格式 仅文本(UTF-8) 支持文本和二进制数据
浏览器兼容性 不支持 IE/Edge(旧版) 现代浏览器广泛支持
自动重连 内置支持 需手动实现
适用场景 服务器主动推送(如通知、实时数据流) 双向实时交互(如聊天、游戏、协作编辑)
复杂度 实现简单 需要处理双向通信和协议细节

如何选择 SSE 或 WebSocket?

  1. SSE 适用场景:更适合简单的服务器推送场景,依赖 HTTP 且实现成本低

    • 只需服务器单向推送数据(如股票行情、新闻更新、日志流)。
    • 希望利用 HTTP 生态(如身份验证、缓存、代理兼容性)。
    • 需要快速实现且无需复杂交互。
  2. WebSocket 适用场景:适合需要双向交互和复杂实时功能的场景,但需处理更多协议细节

    • 需要双向实时通信(如在线聊天、多人游戏、协同编辑)。
    • 需要传输二进制数据(如音视频流)。
    • 对低延迟要求极高。

根据需求选择合适的技术,必要时两者也可结合使用(如用 WebSocket 处理双向交互,SSE 处理单向推送)。

示例

SSE 服务端

  • main.go
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 客户端

  • static/index.html
<!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();
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数