我有许多脚本在执行时回显进度,因为它们长时间运行。每个处理的每个循环数据行的末尾基本上都执行以下操作:
echo '.';
@ob_flush();
flush();
这种方法运行良好多年,然后我在几台服务器上升级到PHP 5.3.x和Apache 2.2.x.现在,即使我用空格填充缓冲区或设置“ob_implicit_flush(1)”,我也无法通过命令显示输出。
一台服务器仍显示输出,但它是块状的。它可能需要将近5分钟,然后屏幕上突然出现一串点。对于其他服务器,在脚本完成执行之前我什么也得不到。
我已经尝试查看php.ini和httpd.conf文件,看看我是否能弄清楚不同服务器之间的变化,但我显然错过了一些东西。
我也尝试在.htaccess中为受影响的脚本禁用mod_deflate,但这也无济于事(禁用用于立即解决问题的mod_gzip)。
有人可以指出我正确的方向吗?无法实时监控脚本执行会导致各种问题,但我们不能再继续使用这些旧的PHP版本了。
在一个更奇特的方面,我确实尝试将服务器降级到PHP 5.2.17,但在降级后仍然存在输出缓冲问题。这让我怀疑它与Apache处理PHP输出的方式有关,因为Apache 2已经存在。
答案 0 :(得分:13)
ob_flush()(flush())只刷新PHP缓冲区 - 网络服务器本身维护一个缓冲区。虽然听起来很奇怪,但提前刷新缓冲区实际上会降低服务器的吞吐量,因此最近版本的apache会更加积极地缓冲。使用HTTP分块编码时,还存在与压缩和部分渲染相关的可怕问题。
如果要逐步向页面添加内容,请使用ajax或websockets一次添加一些内容。
答案 1 :(得分:5)
此问题更多地与您的服务器(apache)而不是php版本有关。
一种选择是禁用输出缓冲,但网站其他部分的性能可能会受到影响
从服务器配置中设置php ini指令(output_buffering=off
),包括.htaccess文件。所以我在.htaccess
文件中使用以下内容来禁用仅为该文件的output_buffering:
<Files "q.php">
php_value output_buffering Off
</Files>
然后在我的静态服务器配置中,我只需要AllowOverride Options=php_value
(或更大的锤子,如AllowOverride All
),以便在.htaccess
文件中允许这样做。
禁用Nginx的缓冲(将“proxy_buffering off;”添加到配置文件并重新启动Nginx
答案 2 :(得分:5)
原始问题中描述的更改很可能是新设置使用的是FastCgi(http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html),并且启用了缓冲功能。
但还有其他因素需要检查:
如果你正在使用Fcgid,那么这也有缓冲:http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize
如果PHP和Apache之间的字符集不匹配 - 这可以解决问题。
mod_deflate和mod_gzip也是缓冲区(如原始问题中所述)
所以要检查的步骤是:
刷新PHP缓冲区 - (如问题中所述)
关闭Apache缓冲 - 在.htaccess
php_value output_buffering off
禁用缓冲用于通缩的mods - 禁用mod_deflate和mod_gzip
确保您的char编码在PHP和Apache之间匹配 -
在php.ini中添加default_charset = "utf-8";
,在httpd.conf中添加AddDefaultCharset utf-8
在FastCgi或Fcgid中禁用缓冲 - 您可以通过添加-flush选项在FastCgi中禁用缓冲。上面链接中的详细信息。上面也列出了Fcgid的选项。
据我所知,这些是服务器上唯一的缓冲区;显然,服务器和浏览器之间的其他设备也可以缓冲,例代理可以在传递之前等待提供完整输出。小提琴手(https://www.telerik.com/fiddler)做到这一点,经常让我知道,直到我记得。
答案 3 :(得分:5)
此问题可能会解决您不需要编辑现有脚本或修改服务器配置以停止缓冲输出的问题。通过使用包装器脚本,您可以在后台从提供Web请求的php脚本开始长时间运行的进程。然后,您可以将长时间运行的进程的输出通过管道传输到文本文件中,该文件可以通过轮询轻松读取以查找脚本的当前进度。示例如下:
<?php
// long_process.php
echo "I am a long running process ";
for ($i = 0; $i < 10; $i++) {
echo ".";
sleep(1);
}
echo " Processing complete";
?>
<?php
// proc_watcher.php
$output = './output.txt';
if ($_GET['action'] == 'start') {
echo 'starting running long process<br>';
$handle = popen("nohup php ./long_process.php > $output &", 'r');
pclose($handle);
} else {
echo 'Progress at ' . date('H:i:s') . '<br>';
echo file_get_contents($output);
}
$url = 'proc_watcher.php';
?>
<script>
window.setTimeout(function() {
window.location = '<?php echo $url;?>';
}, 1000);
</script>
如果向proc_watcher.php?action=start
发送Web请求,脚本应该在后台启动长时间运行的进程,然后每秒将输出文件的内容返回到Web浏览器。
这里的技巧是命令行nohup php ./long_process.php > ./output.txt &
,它在后台运行进程并将输出发送到文件而不是STDOUT。
答案 4 :(得分:0)
我尝试了一切来实现这一点,包括上面列出的所有已知设置。我一直在尝试使用PHP来使用HTTP_RANGE来提供分块视频文件,但它无法正常工作。
将大部分头发拉出后,我找到了答案:你必须输出至少比缓冲区大小多一个字节才能输出到浏览器。这是最终为我工作的脚本:
<?php
// Close open sessions
session_write_close();
// Turn off apache-level compression
@apache_setenv('no-gzip', 1);
// Turn off compression
@ini_set('zlib.output_compression', 0);
// Turn error reporting off
@ini_set('error_reporting', E_ALL & ~ E_NOTICE);
// Tell browser not to cache this
header("Cache-Control: no-cache, must-revalidate");
// close any existing buffers
while (ob_get_level()) ob_end_clean();
// Set this to whatever you like
$buffer = 8096;
for($i = 1; $i <= 8; $i++)
{
// Start a output buffer with specified size
ob_start(null,$buffer,PHP_OUTPUT_HANDLER_FLUSHABLE);
// Output exactly one byte more than that size
// \n == 2 bytes, so 8096-1+2 = 8097
echo str_repeat('=', $buffer-1)."\n";
// 0.25s nap
usleep(250000);
// End output buffering and flush it
ob_end_flush();
flush();
}
希望这有助于某人!