frankenphp用sse推数据到前端看板需:在.frankenphp.yaml中显式配置eventsource: true路由;php脚本设text/event-stream响应头、禁用缓冲、循环调用ob_flush()和flush();前端用eventsource监听message事件并处理重连。

FrankenPHP 里怎么用 Server-Sent Events(SSE)推数据到前端看板
FrankenPHP 本身不提供“推送服务”,但支持标准 PHP 运行时,所以你得自己实现 SSE 流式响应。关键不是 FrankenPHP 特有功能,而是它对长连接和输出缓冲的处理比传统 PHP-FPM 更友好——只要别让 output_buffering 拦住数据就行。
常见错误是:后端写了 echo "data: ...\n\n",前端 EventSource 却收不到任何事件,或者只收到一次就断开。这通常是因为 PHP 缓冲没关、响应头缺失,或 FrankenPHP 的默认配置把 chunked 响应吞了。
- 必须手动关闭输出缓冲:
ob_end_flush()+ob_implicit_flush(true),且不能依赖ini_set("output_buffering", "off")(它在 FrankenPHP 中可能无效) - 响应头必须包含:
header('Content-Type: text/event-stream')和header('Cache-Control: no-cache'),少一个都会导致浏览器拒绝接收 - 每条消息末尾必须是两个换行符(
\n\n),中间不能混用\r\n,否则 Chrome 会静默丢弃整条消息 - FrankenPHP 默认启用 HTTP/2,而某些老版本客户端 EventSource 在 HTTP/2 下有重连 bug;如遇频繁断连,可临时加
header('X-Accel-Buffering: no')禁用 Nginx 层缓存(如果前面套了反代)
PHP 脚本里怎么安全地循环发数据,又不卡死进程
SSE 不是 WebSocket,没有双向通道,所以 PHP 进程得一直挂着等新数据——但直接 while (true) + sleep(1) 容易吃光 worker 连接数。FrankenPHP 的 worker 是有限的,默认只有 4 个,一个 SSE 占一个,4 个并发就撑满。
真正可行的做法是“按需拉取+轻量轮询”,而不是真·长连接轮询。比如:
- 用
pcntl_signal()或declare(ticks=1)做信号中断,避免单请求无限阻塞(但 FrankenPHP 对 pcntl 支持有限,慎用) - 更稳妥的是结合
stream_select()监听 Redis Pub/Sub 或本地 socket,有新数据才发,没数据就usleep(50000)(50ms),降低 CPU 占用 - 务必加超时控制:
set_time_limit(300),并检查connection_aborted(),否则断连的客户端会一直占着 worker - 不要在 SSE 脚本里做 heavy SQL 查询,先查好数据再 echo;否则每次发一条都查一次库,延迟和负载双爆炸
前端 EventSource 怎么处理重连和错误,避免看板卡在旧数据
浏览器原生 EventSource 会在连接断开后自动重试(默认 3 秒),但这个行为不可控,而且不会告诉你断在哪一步。一旦后端发错格式(比如漏了 id: 字段),重连后会从头开始,造成重复或跳号。
- 给每条消息加
id:字段(如id: 12345\n),前端可用eventSource.lastEventId记录位置,重连时带上headers: { 'Last-Event-ID': lastId }(需后端配合解析) - 监听
error事件,但别直接eventSource.close();应该等 5 秒后再new EventSource(),防止雪崩重连 - 用
message事件而非自定义类型名(如data:后跟 JSON 字符串),兼容性最好;解析前先try/catch JSON.parse(),失败就跳过,别让单条脏数据崩掉整个看板 - FrankenPHP 返回 5xx 错误时,
EventSource可能静默重试而不触发error,建议后端在响应体开头写个retry: 10000\n控制重连间隔
为什么用 Redis + SSE 比直连数据库更稳
因为 FrankenPHP 的 PHP 进程是常驻的,但数据库连接不是。MySQL 的 wait_timeout 默认 8 小时,而你的 SSE 连接可能只活 5 分钟,中间网络抖动、DB 重启、连接池回收都会让 mysqli_query() 突然失败。直接查库的 SSE 脚本上线三天必出 MySQL server has gone away。
- 用 Redis 的
PUBLISH/PSUBSCRIBE做数据中转:采集服务写 Redis,SSE 脚本只订阅频道,完全解耦 DB 稳定性 - Redis 消息体建议用简单结构:
{"ts":1717023456,"metric":"cpu","value":72.4},别嵌套太深,PHPjson_decode()解析快 - 别用
redis->subscribe()阻塞式调用——FrankenPHP 不支持真正的异步 I/O;改用redis->pubSubLoop()或轮询redis->get()配合EXPIREkey - 如果数据源本身是 Prometheus,直接用
curl轮询/api/v1/query?query=...更轻量,比连 Redis 还少一层依赖
最麻烦的从来不是“怎么推”,而是“怎么确保每条数据只到一次、不乱序、不断连”。FrankenPHP 让 PHP 长连接变得可行,但没帮你绕过 HTTP 协议和浏览器 EventSource 的固有限制。别迷信自动重连,每个 echo 都得带容错,每个 header 都得亲手核对。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











