You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Server-Sent Events时,服务端是否必须用循环返回多值?

关于PHP中Server-Sent Events(SSE)的实践建议

嘿,刚接触SSE的时候确实容易被那些循环+刷缓冲区的示例绕晕,我当初也踩过不少坑!先看看你写的这段代码,咱们一步步捋清楚问题和优化点:

你原有的PHP代码

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// Output a 'waiting message'
$progress = 0;
while ($progress < 100) {
    echo 'data:';
    echo $progress;
    echo "\n\n";
    flush();
    ob_flush(); //I tried ob_implicit_flush(true) too
    $progress++;
    sleep(1);
}
?>

几个关键问题和优化方向

  • 缓冲区刷新顺序搞反了
    你先调用了flush()ob_flush(),这是不对的。正确顺序应该是先ob_flush()(把PHP应用层的输出缓冲区内容推送到服务器缓冲区),再flush()(把服务器缓冲区内容推送到客户端)。顺序反了的话,很多环境下数据根本没法实时传到前端。
  • ob_implicit_flush(true)的正确用法
    开启这个选项后,PHP会自动刷新应用层缓冲区,你就不用每次循环都手动调用ob_flush()了。不过开启前最好先清空现有缓冲区,避免残留内容干扰。
  • 缺少维持长连接的响应头
    加上Connection: keep-alive头很重要,它能告诉服务器和客户端要维持这个长连接,不要随便断开。
  • 数据格式建议用JSON
    直接输出数字虽然能工作,但如果后续要传递更复杂的状态(比如进度描述、错误信息),用JSON格式会让前端处理更方便。

修正后的PHP代码

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive'); // 维持长连接必备

// 开启隐式自动刷新,省去每次手动ob_flush()
ob_implicit_flush(true);
// 清空现有输出缓冲区,避免残留内容
if (ob_get_level() > 0) {
    ob_clean();
}

$progress = 0;
while ($progress <= 100) {
    // 用JSON封装进度数据,前端解析更灵活
    echo 'data: ' . json_encode(['progress' => $progress]) . "\n\n";
    
    // 部分服务器(比如Apache)可能仍需要手动调用flush()确保数据推送
    flush();
    
    // 进度到100后就不用再sleep了,直接结束循环
    if ($progress < 100) {
        sleep(1);
    }
    $progress++;
}
?>

配套的完整JavaScript代码

// 建立SSE连接,替换成你的PHP脚本路径
const sseConnection = new EventSource('your-sse-progress.php');

// 接收服务器推送的消息
sseConnection.onmessage = function(event) {
    try {
        const progressData = JSON.parse(event.data);
        console.log('当前进度:', progressData.progress);
        
        // 这里可以更新页面上的进度条,比如:
        // document.getElementById('progress-bar').style.width = `${progressData.progress}%`;
        
        // 进度完成后关闭连接,避免资源浪费
        if (progressData.progress === 100) {
            sseConnection.close();
            alert('任务已完成!');
        }
    } catch (err) {
        console.error('解析SSE数据失败:', err);
    }
};

// 处理连接错误
sseConnection.onerror = function(error) {
    console.error('SSE连接出现问题:', error);
    sseConnection.close();
};

额外注意事项

  • 服务器配置坑:如果用Apache,要确保php.ini里的output_buffering设置为Off;如果是Nginx,要针对SSE路由关闭proxy_bufferingfastcgi_buffering,否则服务器会把你的输出攒起来一起发,完全失去SSE的实时性。
  • 心跳机制:有些代理或者服务器会自动断开超过30秒没有数据的长连接,所以如果是持续的SSE服务,建议每隔25秒左右发送一条心跳消息:echo 'data: ping\n\n'; flush();,避免连接被断开。
  • 资源清理:如果用户中途关闭页面,后端的循环可能还在跑,你可以通过检查connection_aborted()来终止循环,比如在循环里加一句:if (connection_aborted()) break;

内容的提问来源于stack exchange,提问作者Jordan Daigle

火山引擎 最新活动