小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2026-04-10 / 1 阅读
0
0

SSE 与 WebSocket 深度解析

Web 实时通信技术深度解析:SSE 与 WebSocket 学习笔记

一、 核心概念概述与技术选型

1.1 传统 HTTP 的局限性

传统的 HTTP 请求是“一问一答”的形式(客户端请求,服务器响应后断开)。为了实现服务器向客户端的主动数据推送,打破单向被动响应的限制,诞生了 SSE 和 WebSocket 两种实时通信技术。

1.2 核心概念对比

  • SSE (Server-Sent Events) - “广播电台”模型:基于标准 HTTP 协议的单向通信。连接建立后,服务器源源不断地向客户端推送数据流,但客户端不能通过此通道向服务器发送新数据。
  • WebSocket - “打电话”模型:基于 TCP 的双向全双工通信协议。连接建立后,客户端和服务器随时可以互相发送数据,地位平等。

💡 用户观点与场景决策:

在探讨“系统通知小红点”或“实时图文直播”的场景选型时,用户准确指出:“SSE 单向接受,因为用户不需要通过这个通道回复。” 这精准命中了 SSE 的核心优势——针对轻量级、被动接收的场景,使用原生自带断线重连且无惧防火墙拦截的 SSE 是最佳选择。


二、 SSE (单向数据流) 的深层原理与实战

2.1 原理阐述:如何建立持续的数据流

SSE 100% 纯正基于 HTTP 协议。其原理相当于一次“无限期延长”的 HTTP 下载。通过设置特殊的 HTTP 响应头,服务器指示浏览器不要关闭连接,并将接收到的特定格式文本解析为事件流。

PHP 服务端代码示例:

PHP

<?php
// 1. 设置头部,开启事件流
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // 禁用缓存
header('Connection: keep-alive');  // 保持连接

// 2. 发送数据 (格式必须严格遵循 "data: 内容 \n\n")
$messageId = 101; 
echo "id: {$messageId}\n";
echo "data: 这是第101条消息\n\n";

// 3. 清空输出缓存,立即推送
ob_flush(); flush(); 
?>

JavaScript 客户端代码示例:

JavaScript

const eventSource = new EventSource('server.php');
eventSource.onmessage = function(event) {
    console.log("收到服务器推送的数据:", event.data);
};

2.2 原理阐述:自动重连与断点续传机制

  • 自动重连概念: 浏览器 EventSource 原生支持网络闪断后的后台静默重连,无需手动编写重连逻辑。
  • 断点续传原理: 服务器在下发数据时携带 id: 字段(相当于书签)。当浏览器断线重连时,会在新的 HTTP 请求头中自动添加 Last-Event-ID 字段。PHP 后端通过读取 $_SERVER['HTTP_LAST_EVENT_ID'] 即可得知客户端错过的消息,实现无缝补发。

2.3 深度探讨:LLM 大模型场景与消息持久化

❓ 用户提问整合:

  1. SSE 是通过纯 HTTP 请求发送的吗?大语言模型平台如何处理?
  2. messageId 真的需要存储到数据库吗?
  3. 大模型输出完毕时,服务器如何通知前端关闭连接?
  • 大语言模型应用原理: AI 打字机效果底层多采用 SSE。模型每生成一个 Token,后端即刻将其包装为 SSE JSON 格式推给前端追加显示。
  • 持久化策略 (是否存储 messageId):
    • 需要严格送达 (系统通知/交易状态): 必须持久化,通常存入 Redis Stream/List 或 Kafka 以应对高并发补发。
    • 时效性至上 (AI打字机/股票大盘): 无需入库。用户断线重连只需获取“此刻最新状态”,断网期间的历史数据作废,PHP 端可忽略 Last-Event-ID
  • 结束信号机制: SSE 协议未规定强制结束语法,通常由业务层约定。
    • 方法1 (主流): 发送特定文本暗号(如 OpenAI 的 data: [DONE]\n\n),前端拦截到该文本时执行 eventSource.close() 主动掐断连接。
    • 方法2: 自定义事件名,如 event: end,前端专门监听该事件。

三、 WebSocket (双向通道) 的深层原理与实战

3.1 概念与原理:协议升级与握手 (Handshake)

WebSocket 底层基于 TCP,但其首次建立连接是借用标准 HTTP 请求完成的

原理在于 HTTP 请求头中的 Upgrade: websocketConnection: Upgrade 字段,向服务器申请“升级协议”。同时附带 Sec-WebSocket-Key 验证服务器支持该协议。服务器验证后返回 101 Switching Protocols 状态码,HTTP 历史使命结束,正式切换为 TCP 双向长连接。

3.2 原理阐述:服务端架构转变与事件驱动

传统的 PHP-FPM 为“短连接”设计(运行完毕立即销毁)。而 WebSocket 必须要求进程常驻内存。因此需借助 CLI 命令行模式下的工具(如 Workerman / Swoole)。

PHP WebSocket 服务端核心骨架 (Workerman 风格):

PHP

<?php
$server = new WebSocketServer("0.0.0.0:8080");

// 建立连接事件
$server->on('Connect', function($connection) { ... });

// 收到消息事件
$server->on('Message', function($connection, $data) {
    $connection->send("服务器收到: " . $data);
});

$server->start();
?>

3.3 深度探讨:群聊广播与私聊的身份映射

💡 用户观点提取:

关于如何实现消息广播,用户指出需要“保存发给谁也就是群聊中的人”。关于私聊,用户指出在连接时需要“记录 userid 方便私聊发送”。

  • 群聊广播原理: 服务器在内存中维护一个全局“花名册”(关联数组 $roster = [])。新用户连接时存入该数组,发消息时 foreach 遍历该数组进行 $connection->send()
  • 私聊身份映射原理: 默认连接仅包含无意义的网络 ID。前端在连接建立时(通过 URL 参数或首条鉴权 JSON 消息)传递真实身份标识。后端将花名册的键名替换为真实的 UserId,实现精准寻址:$roster[$userId] = $connection

四、 生产环境部署原理与 Nginx 避坑指南

4.1 Nginx 代理机制与协议识别

💻 用户提供的核心配置记录:

proxy_pass http://backend;

proxy_http_version 1.1;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection "upgrade";

原理剖析: 这段配置使得 Nginx 能够识别 HTTP 握手请求中的升级意图。Nginx 作为公网大门(通常监听 80/443),作为透明管道将握手请求及后续 TCP 数据流转发至内网 PHP 独立进程监听的端口(如 8080)。

4.2 核心避坑:两大隐形陷阱与原理

  1. SSE 陷阱:打字卡顿现象
    • 原理: Nginx 默认开启代理缓冲 (Proxy Buffering),为节约网络资源会截留数据直至凑够一定体积(如 4KB)才下发,破坏了实时字符流输出。
    • 解决: PHP 下发头部 header('X-Accel-Buffering: no'); 或 Nginx 配置 proxy_buffering off;
  2. WebSocket 陷阱:离奇失联 (60秒魔咒)
    • 原理: Nginx 的 proxy_read_timeout 默认为 60 秒,无数据流动会被判定为死连接并强制斩断。
    • 解决: 心跳机制 (Heartbeat)。用户指出这是“发送活动报文”。前端通过 setInterval 定期发送极小体积的 JSON (Ping),后端回复 (Pong),以保持通道活跃,重置 Nginx 倒计时。

4.3 安全加密策略 (WSS 与 混合内容拦截)

❓ 用户提问与思考:

为什么必须用 Nginx 而不直接连后端 8080 端口?用户准确回答出浏览器安全机制:“拦截 CORS (准确讲为混合内容 Mixed Content拦截)”。

  • 加密原理: 现代网站均使用 https://。如果在安全环境下发起明文的 ws:// 请求,会被浏览器以安全策略强行拦截。必须升级为 wss:// (WebSocket Secure)。
  • 架构分工: Nginx 负责持有 SSL 证书并进行“SSL 卸载”,解密外网的 wss:// 数据,然后通过内网以明文 ws:// 转发给后端的 PHP 进程,实现安全与性能的解耦。

五、 分布式架构进阶:高并发集群与 Redis 通信

随着在线人数激增,单台 PHP 服务器遭遇内存与连接数瓶颈,必须扩展为多台服务器集群(Server A, B, C)。

5.1 核心痛点:内存孤岛效应

每台服务器只维护连接到自己机器上的本地“花名册”。Server A 无法直接将消息发送给连接在 Server B 上的用户。

5.2 解决方案原理:Redis Pub/Sub (发布/订阅)

引入 Redis 作为中央通信枢纽:

  1. 订阅 (Subscribe): 所有 Server 启动时订阅 Redis 的同一全局频道。
  2. 发布 (Publish): Server A 收到用户消息后,不再直接处理,而是打包推送给 Redis 频道。
  3. 广播与分发: Redis 瞬间将消息同步给所有 Server,每台 Server 监听到消息后,再遍历本地“花名册”下发给用户。

5.3 深度机制:防回音 (Echo Prevention)

💡 用户架构设计思路:

“利用哈希表存储连接 id 确保不会发送回去”

防回音处理原理: 当 Server A 将消息推给 Redis 后,Redis 会广播给所有节点(包含 Server A 自身)。为了防止发送者收到自己发出的重复消息:

  1. 数据包封装: Server A 推送到 Redis 的 Payload 必须包含发送者的身份标识 (sender_id)。
  2. 分发过滤: Server A 从 Redis 监听到广播,在遍历本地 $roster 下发时,执行比对:如果遍历到的 $userId === $sender_id,则 continue 跳过,实现精准去重。

六、 综合回顾与技术选型总结表

核心维度 SSE (单向广播) WebSocket (双向通话)
数据流向 单向:服务器 ➔ 客户端 双向:服务器 ⬌ 客户端
底层通信 标准 HTTP 协议 独立协议 (仅首次握手借用 HTTP)
断线重连 浏览器原生自动重连 (EventSource) 需前端手动编写 JS 逻辑恢复连接
PHP 实现环境 普通 Web 脚本 (fpmheader+flush) 需 CLI 常驻内存进程 (Workerman/Swoole)
典型黄金场景 AI 对话打字机、系统通知红点、股票大盘 网页在线客服、多人实时游戏、协同编辑文档

评论