PHP 怎么SRC 响应

wen PHP项目 1

本文目录导读:

PHP 怎么SRC 响应

  1. 方式一:最简单直接的 while + flush()(适合新手理解)
  2. 方式二:使用框架特性(以 Laravel 为例)
  3. 方式三:使用 SwooleWorkerman(生产环境推荐)
  4. 方式四:使用 SSE 库(如 php-sse
  5. 关键细节注意事项(踩坑必看)
  6. 总结对比表

在 PHP 中实现 SSE(Server-Sent Events,服务器发送事件),其实质是持续输出 HTTP 响应,并设置特定的 Content-Type缓存控制 头。

由于 PHP 是脚本语言,默认执行完就结束,要实现 SSE,必须打破脚本执行上限及时刷新缓冲区

以下是 4 种主流实现方式,按复杂度和使用场景排序:


最简单直接的 while + flush()(适合新手理解)

这是最纯粹的 SSE 写法,不依赖任何框架。核心逻辑:set_time_limit(0) 防止超时,用 ob_end_flush() 关闭输出缓冲,用 flush() 强制推送。

<?php
// 1. 必须设置响应头
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // 禁用 Nginx 缓冲(如果用了 Nginx)
// 2. 关闭执行超时(PHP 默认 30 秒超时)
set_time_limit(0);
// 3. 核心:关闭 PHP 输出缓冲,让数据立刻发送
// 如果之前有开启 ob_start(),需要先清理掉
while (ob_get_level() > 0) {
    ob_end_clean();
}
// 4. 主循环:无限推送数据
$counter = 0;
while (true) {
    // 构建 SSE 格式的数据(必须是 "data: ...\n\n" 格式)
    echo "id: " . $counter . "\n";
    echo "data: " . json_encode(['time' => date('H:i:s'), 'count' => $counter]) . "\n\n";
    // 强制刷新输出缓冲区到浏览器
    flush();
    // 防止 CPU 跑满,休眠 1 秒
    sleep(1);
    $counter++;
    // 可选:设置退出条件(比如客户端断开检测)
    if (connection_aborted()) {
        break;
    }
}
?>

使用框架特性(以 Laravel 为例)

在 Laravel 中,你可以用 response()->stream() 实现,语法更优雅,且会自动处理响应头。

// routes/web.php 或 routes/api.php
Route::get('/sse', function () {
    return response()->stream(function () {
        // 关闭缓冲区
        while (ob_get_level() > 0) {
            ob_end_clean();
        }
        $i = 0;
        while (true) {
            echo "event: ping\n";
            echo "data: " . json_encode(['message' => 'Hello ' . $i]) . "\n\n";
            ob_flush();
            flush();
            if (connection_aborted()) break;
            sleep(1);
            $i++;
        }
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'X-Accel-Buffering' => 'no',
    ]);
});

使用 SwooleWorkerman(生产环境推荐)

如果追求高性能长连接数以万计,原生 PHP 的 apache + mod_phpphp-fpm 会因为每个连接占用一个进程而内存耗尽。

推荐使用常驻内存框架

// 使用 Swoole 示例
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$server = new Server("0.0.0.0", 9501);
$server->on('Request', function (Request $request, Response $response) {
    // 设置 SSE 头
    $response->header('Content-Type', 'text/event-stream');
    $response->header('Cache-Control', 'no-cache');
    // 发送初始数据
    $response->write("retry: 1000\n\n");
    // 定时器每秒推送数据(Swoole 的 tick 是异步非阻塞的)
    $timerId = swoole_timer_tick(1000, function () use ($response) {
        $data = json_encode(['time' => date('H:i:s')]);
        $response->write("data: {$data}\n\n");
        // 如果客户端断开,停止定时器
        if (!$response->isWritable()) {
            swoole_timer_clear($timerId);
        }
    });
});
$server->start();

使用 SSE 库(如 php-sse

对于复杂的推送场景(如多频道、断线重连、事件广播),可以直接用现成库:

composer require sse/sse
require 'vendor/autoload.php';
use Sse\Event;
use Sse\SSE;
class MyEvent extends Event {
    public function update() {
        // 返回要推送的数据
        return json_encode(['data' => date('H:i:s')]);
    }
    public function check() {
        // 检查是否需要推送
        return true;
    }
}
$sse = new SSE();
$sse->addEventListener('my_event', new MyEvent());
$sse->start();

关键细节注意事项(踩坑必看)

Nginx 缓冲问题(最常见坑)

如果不加 X-Accel-Buffering: no,Nginx 会缓冲你的输出,直到脚本结束才发给客户端,导致“实时性”失效。

header('X-Accel-Buffering: no');
// 或者 Nginx 配置 fastcgi_buffering off;

Apache 压缩问题

如果开启了 gzip,SSE 数据流会被压缩,浏览器可能无法实时解析,需要关闭压缩:

SetEnv no-gzip 1

PHP-FPM 超时问题

不仅要在脚本里 set_time_limit(0),还要在 php.ini 里设置:

max_execution_time = 0

连接断开的处理

客户端刷新或关闭页面时,PHP 不会立刻停止,要定期检查:

if (connection_aborted()) {
    // 结束循环,释放资源
    break;
}

如果使用 Swoole,则监听 close 事件。

数据格式标准

必须严格遵守 SSE 协议:

  • 每条数据前必须有 data: 前缀
  • 必须以空行 \n\n
  • 可选字段:id:(断线重连依据)、event:(自定义事件名)、retry:(重连间隔)

总结对比表

方式 并发能力 稳定性 适用场景
原生态 while 低(~几百) 中等 学习、内网小工具
Laravel Stream 中(~几千) 基于框架的业务推送
Swoole/Workerman 高(~几万) 很高 生产环境、金融行情、实时聊天
第三方库 快速集成,不想写底层

建议:

  • 如果只是搞个 Demo 或内部系统,方式一就够了。
  • 如果是线上正式项目且用 PHP-FPM,一定要用方式二(框架封装)并处理好 Nginx 缓冲。
  • 如果是高并发实时推送(如股票、在线人数),建议直接用 Node.js 或 Go,PHP 做 SSE 开销较大,如果必须用 PHP,强烈建议 Swoole 常驻内存方案。

抱歉,评论功能暂时关闭!