SSE 协议中的心跳重连与超时重连机制详解

核心机制概述

在 SSE 协议中,客户端需要处理两种重要的重连场景:

  • 心跳重连:主动检测连接是否存活
  • 超时重连:响应网络或服务器超时

以下是详细实现机制和决策流程图:

mermaid.png
graph TD
    A[建立SSE连接] --> B{连接状态}
    B -->|活跃| C[接收数据]
    B -->|空闲| D[启动心跳检测]
    C --> E{数据流中断?}
    E -->|是| F[触发超时重连]
    E -->|否| B
    D --> G{收到心跳?}
    G -->|是| B
    G -->|否| H[心跳超时]
    H --> F
    F --> I[计算重连延迟]
    I --> J[延迟重连]
    J --> K[尝试重连]
    K --> L{重连成功?}
    L -->|是| B
    L -->|否| M[指数退避]
    M --> I

一、心跳机制实现

目的

防止中间设备(代理、防火墙)因空闲而关闭连接

实现方案

服务器端心跳

// Node.js 示例
setInterval(() => {
  res.write(': heartbeat\n\n'); // 发送注释行作为心跳
}, 15000); // 每15秒发送一次

客户端心跳检测

// LDSwiftEventSource 实现
class EventSource {
    private var heartbeatTimer: Timer?
    private let heartbeatInterval: TimeInterval = 20
    
    func startHeartbeatMonitor() {
        heartbeatTimer = Timer.scheduledTimer(withTimeInterval: heartbeatInterval, 
                                             repeats: true) { [weak self] _ in
            if self?.lastMessageDate.timeIntervalSinceNow ?? 0 < -self.heartbeatInterval {
                self?.handleHeartbeatTimeout()
            }
        }
    }
    
    private func handleHeartbeatTimeout() {
        closeConnection(reason: .heartbeatTimeout)
        scheduleReconnect()
    }
}

心跳决策表

条件 动作 重连延迟
15秒内收到数据 重置计时器 -
15-20秒无数据 警告日志 -
>20秒无数据 断开重连 1秒
>30秒无数据 视为连接死亡 3秒

二、超时重连机制

超时类型及处理

  1. 连接超时(TCP层)

    • 原因:DNS失败、服务器宕机
    • 处理:立即重试(1秒延迟)
  2. 读取超时(应用层)

    • 原因:服务器停止发送
    • 处理:指数退避重连
  3. 写入超时

    • 原因:网络拥塞
    • 处理:线性增加重试间隔

指数退避算法实现

class EventSource {
    private var reconnectAttempts = 0
    private let initialReconnectDelay: TimeInterval = 1.0
    private let maxReconnectDelay: TimeInterval = 60.0
    
    func scheduleReconnect() {
        reconnectAttempts += 1
        
        // 计算退避延迟
        let delay = min(
            initialReconnectDelay * pow(2.0, Double(reconnectAttempts)), 
            maxReconnectDelay
        )
        
        DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
            self.connect()
        }
    }
    
    func resetReconnectState() {
        if Date().timeIntervalSince(lastConnectedDate) > backoffResetThreshold {
            reconnectAttempts = 0
        }
    }
}

超时决策矩阵

错误类型 状态码 是否重连 重连延迟策略
DNS解析失败 - 线性增加
TCP连接超时 - 指数退避
TLS握手失败 - 固定间隔
HTTP 401 401 -
HTTP 403 403 -
HTTP 404 404 -
HTTP 429 429 使用Retry-After头
HTTP 500 500 指数退避
HTTP 502/503/504 502+ 指数退避

三、连接断开决策点

应主动断开连接的情况

  1. 客户端主动关闭

    eventSource.close()
    
  2. 服务器指示关闭

    • HTTP 204 No Content
    • HTTP 401 Unauthorized
  3. 致命协议错误

    case invalidContentType
    case invalidUTF8Stream
    case frameSizeExceeded
    
  4. 资源管理需求

    • 应用进入后台
    • 电池电量不足

应保持连接的情况

  1. 短暂网络波动

    • 自动重连机制处理
  2. 服务器维护

    • HTTP 503 + Retry-After
  3. 客户端休眠唤醒

    // AppDelegate.swift
    func applicationDidBecomeActive(_ application: UIApplication) {
        eventSource.reconnect()
    }
    

四、完整重连流程图

mermaid.png
sequenceDiagram
    participant Client
    participant Network
    participant Server
    
    Client->>Server: 发起SSE连接
    Server-->>Client: 200 OK / text/event-stream
    
    loop 活动连接
        Server->>Client: 定期发送数据/心跳
        Client->>Client: 重置超时计时器
    end
    
    alt 心跳超时
        Client->>Client: 检测到20秒无数据
        Client->>Server: 关闭连接
        Client->>Client: 调度1秒后重连
    else 读取超时
        Network--xClient: 网络中断
        Client->>Client: 检测到30秒无数据
        Client->>Server: 关闭连接
        Client->>Client: 调度指数退避重连
    end
    
    Client->>Server: 重连请求
    alt 重连成功
        Server-->>Client: 200 OK
        Client->>Client: 重置重连计数器
    else 重连失败
        Server-->>Client: 503 Service Unavailable
        Client->>Client: 增加重连计数器
        Client->>Client: 计算新延迟时间
        Client->>Server: 延迟后再次重连
    end

五、最佳实践建议

客户端配置参数

参数 推荐值 说明
初始重连延迟 1000ms 首次重连等待时间
最大重连延迟 30000ms 最大等待时间
退避重置阈值 60000ms 连接稳定多久重置计数器
心跳检测间隔 15000ms 检查连接活跃频率
读取超时阈值 30000ms 无数据多久视为超时

服务器端实现建议

  1. 心跳机制

    setInterval(() => {
      res.write(`: ${Date.now()}\n\n`);
    }, 15000);
    
  2. 重连指导

    // 429 Too Many Requests
    res.setHeader('Retry-After', '10'); // 10秒后重试
    
  3. 连接关闭协议

    // 正常关闭
    res.write('event: end\ndata: goodbye\n\n');
    res.end();
    

异常处理增强

func onError(error: Error) {
    switch error {
    case let sseError as SSEError where sseError.isFatal:
        closePermanently()
        
    case let urlError as URLError where urlError.code == .timedOut:
        scheduleReconnect(delay: 3.0)
        
    case let httpError as HTTPError where httpError.statusCode == 429:
        if let retryAfter = httpError.headers["Retry-After"] {
            scheduleReconnect(delay: Double(retryAfter))
        } else {
            scheduleReconnect()
        }
        
    default:
        scheduleReconnect()
    }
}

六、性能优化策略

  1. 自适应心跳

    func adjustHeartbeatInterval() {
        let networkQuality = estimateNetworkQuality()
        heartbeatInterval = networkQuality > 0.8 ? 30.0 : 10.0
    }
    
  2. 优先级队列

    let reconnectQueue = DispatchQueue(
        label: "com.yourapp.sse.reconnect", 
        qos: .utility
    )
    
  3. 电池感知

    NotificationCenter.default.addObserver(
        self, 
        selector: #selector(batteryStateChanged),
        name: UIDevice.batteryStateDidChangeNotification,
        object: nil
    )
    
    @objc func batteryStateChanged() {
        if UIDevice.current.batteryState == .unplugged {
            maxReconnectDelay = 120.0 // 延长重连间隔省电
        }
    }
    

总结

在 SSE 实现中,健壮的重连机制需要:

  1. 双重检测

    • 主动心跳检测预防中间设备断开
    • 被动超时检测响应网络故障
  2. 智能退避

    • 指数退避避免服务器过载
    • 重置机制恢复连接效率
  3. 精确断连

    • 区分可恢复和不可恢复错误
    • 遵循服务器指示关闭连接
  4. 上下文感知

    • 根据网络质量调整参数
    • 考虑设备状态优化资源使用

通过实现这些机制,SSE 客户端可以保持高效稳定的实时连接,同时优雅地处理各种网络异常场景。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容