SSE 的本质是基于 HTTP 的服务器向客户端单向推送数据的技术。
一、前世:HTTP 早期的 “推送困境” 与替代方案
-
HTTP/0.9(1991):无推送可能
HTTP/0.9 是最原始的版本,仅支持GET方法,且响应仅能是 HTML 文本。它采用 “短连接” 模式:客户端发起请求→服务器返回数据→连接立即关闭。服务器完全没有 “主动推送” 的能力,实时通信无从谈起。 -
HTTP/1.0(1996):长连接萌芽,但仍无原生推送
HTTP/1.0 引入了更多特性(如POST方法、HTTP 头部、多媒体类型),但默认仍是 “短连接”。此时虽可通过Connection: keep-alive头部手动开启 “长连接”(连接复用),但这只是减少了重复建立连接的开销,服务器仍需等待客户端请求才能响应,无法主动推送。
为了实现 “类实时” 效果,早期开发者只能用 “曲线救国” 的方案:
- 轮询(Polling):客户端定时(如每秒)向服务器发送请求,询问是否有新数据。缺点是冗余请求多、延迟高(间隔内的新数据无法及时获取)。
- 长轮询(Long Polling):客户端发起请求后,服务器不立即响应,而是保持连接等待新数据,有数据时才返回;客户端收到响应后立即再次发起请求。相比轮询更高效,但本质仍是 “客户端拉取”,且对服务器资源消耗较大(连接长时间占用)。
二、今生:HTTP/1.1 与 SSE 的诞生 —— 原生推送的突破
HTTP/1.1(1999 年发布)的出现为 SSE 奠定了技术基础。它的两个核心特性直接支撑了 SSE 的实现:
- HTTP/1.1 的关键特性:SSE 的 “基础设施”
- 默认长连接:HTTP/1.1 将Connection: keep-alive设为默认行为,客户端与服务器的连接可以复用,无需每次请求都重新建立 TCP 连接。这为服务器 “持续发送数据” 提供了基础。
- 分块传输编码(Chunked Transfer Encoding):HTTP/1.1 引入Transfer-Encoding: chunked头部,允许服务器将响应数据分成多个 “块”(chunk)逐步发送,且无需预先知道总长度。这解决了 “服务器需要持续推送动态数据(长度未知)” 的核心问题 ——SSE 正是通过分块传输,让服务器可以不断向客户端 “追加” 新数据。
- SSE 的诞生:基于 HTTP/1.1 的原生推送标准
- 2006 年前后,随着实时 Web 应用(如社交网络、实时监控)的兴起,对 “服务器主动推送” 的需求愈发迫切。2008 年,W3C 开始制定 SSE 标准**(最终于 2015 年正式标准化),其设计完全依托 HTTP/1.1 的特性**
- 协议设计:SSE 使用text/event-stream作为 MIME 类型,客户端发起GET请求后,服务器通过长连接持续发送 “事件流”(格式为event: 事件名\ndata: 数据\n\n)。
核心优势:
- 轻量:基于 HTTP,无需引入新协议(如 WebSocket 需要升级协议);
- 自动重连:浏览器原生支持断线后自动重试(通过retry字段控制间隔);
- 单向高效:专注于 “服务器→客户端” 推送,比全双工的 WebSocket 更简单。
此时的 SSE 完全依赖 HTTP/1.1 的长连接和分块传输,成为实时单向推送的 “轻量首选”。
三、演进:HTTP/2 对 SSE 的优化
头部压缩:减少冗余传输
HTTP/2 的HPACK 压缩算法是核心优势之一。在 SSE 场景中,虽然连接是长期保持的,但每次发送事件时仍需携带 HTTP 头部(如<font style="color:rgba(0, 0, 0, 0.85) !important;">Content-Type: text/event-stream</font>
)。HPACK 通过静态表(预定义常见头部)和动态表(缓存已发送的头部)大幅减少头部数据量。例如:
- 静态表:直接复用预定义的头部字段(如
<font style="color:rgb(0, 0, 0);">Connection: keep-alive</font>
),无需重复传输; - 动态表:缓存首次发送的头部(如
<font style="color:rgb(0, 0, 0);">Content-Type</font>
),后续仅需传输索引值。
相比之下,HTTP/1.1 的头部是明文传输,且无法复用动态缓存。以一个简单的 SSE 头部为例:
# HTTP/1.1头部(约100字节)
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
# HTTP/2头部(约20字节)
:content-type = text/event-stream (索引值)
:cache-control = no-cache (索引值)
:connection = keep-alive (索引值)
HPACK 可减少约 80% 的头部开销,尤其在频繁发送小事件(如实时通知)时,节省的带宽和延迟更为显著。
二进制分帧:更高效的解析与传输
HTTP/2 的二进制分帧层将数据分割为更小的帧(Frame),并通过流(Stream)标识符关联到特定 SSE 流。这带来两大优势:
- 解析速度更快:二进制格式比 HTTP/1.1 的文本格式更易被计算机解析,减少 CPU 开销。例如,HTTP/1.1 的
<font style="color:rgb(0, 0, 0);">chunked</font>
分块需手动解析<font style="color:rgb(0, 0, 0);">chunk-size</font>
字段,而 HTTP/2 的帧结构直接包含长度和类型,解析效率更高。 - 传输更灵活:SSE 事件可被拆分为多个帧,服务器可在发送过程中穿插其他控制帧(如流量控制帧),而不影响 SSE 流的连续性。例如,当客户端处理速度较慢时,服务器可通过流量控制帧暂停发送,避免缓冲区溢出。
此外,HTTP/2 的分帧层支持流优先级(Stream Prioritization)。虽然用户仅使用一个 SSE 流,但如果同一连接上有其他资源(如页面的 CSS/JS),优先级机制可确保 SSE 流的实时数据优先传输。这种灵活性是 HTTP/1.1 无法实现的。
TCP 连接优化:更稳定的长连接
HTTP/1.1 的长连接存在以下问题:
- 连接数限制:浏览器通常限制同一域名的并发连接数(如 Chrome 为 6 个),即使仅使用一个 SSE 流,也可能因其他资源占用连接而延迟。
HTTP/2 通过多路复用(Multiplexing)解决了这些问题:
- 单一 TCP 连接:所有数据(包括 SSE 流和其他资源)通过同一连接传输,避免连接数限制