使用PHP和Apache防止输出缓冲

7
我有一个PHP脚本,需要发送大量的记录,并且希望每个记录可用时立即刷新:客户端能够在每个记录到达时处理它,无需等待整个响应。我知道整个传输需要花费更长的时间,因为它需要分成多个数据包发送,但它仍然能够让客户端更早地开始工作。
我已经尝试了所有不同的“flush()”和“ob_flush()”函数,但似乎没有帮助将数据实际发送到终端之前完成页面。我已确认这不是浏览器的问题,因为我已使用telnet进行了测试。
3个回答

7
对我而言,唯一有效的解决方案是将php.ini中的output_buffering指令设置为“Off”。我不想对整个服务器进行更改,只对某个特定资源进行更改。通常,您可以在PHP脚本中使用ini_set来实现这一点,但出于某种原因,PHP不允许以这种方式设置output_buffering(请参见php手册)。
好吧,事实证明,如果您正在使用Apache,则可以从服务器配置(包括.htaccess文件)中设置一些php ini指令(包括output_buffering)。因此,我在.htaccess文件中使用以下代码仅针对一个文件禁用了output_buffering:
<Files "q.php">
    php_value output_buffering Off
</Files>

然后在我的静态服务器配置中,我只需要AllowOverride Options=php_value(或者像AllowOverride All这样更大的锤子)才能允许在一个.htaccess文件中使用它。


1
不确定这个一直是错误的还是PHP5.6有所改变,但我不得不使用php_flag output_buffering Off来使Off起作用。 - RiggsFolly
我将php.ini中的“output_buffering”从4096设置为Off,并重新启动了整个服务器并通过phpinfo()进行验证,但仍然面临同样的问题,服务器在向浏览器发送数据之前等待页面加载。是否还有Apache2或Ubuntu 16.04中的其他设置?我使用的是PHP7。 - Tarik

5
您没有提及使用的Web服务器,但我猜测您可能使用的是Apache2。我遇到了与您描述的几乎相同的问题。 我试图让我的CGI脚本在准备好时立即通过信息,而不是缓冲整个过程。 在curl等中工作很快,但在浏览器(几乎所有浏览器)中会被缓冲,这至少令人发狂。 我按照您描述的确切步骤进行了操作。 在我的情况下,解决方法是修改Apache2中的 sites-enabled/terrifico.com 配置文件(问题行以

SetEnvIfNoCase

(您可以忽略该行上下的内容,我只是为了参考它放置在哪里。)

<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName test.terrifico.com
ServerAlias test.terrifico.com

SetEnvIfNoCase Request_URI \.cgi$ no-gzip dont-vary

DocumentRoot /var/www/test.terrifico.com

从盯着网络流量来回传输,我终于明白了浏览器正在广告它接受任何东西(文本)的紧缩。这就是浏览器和curl之间的区别。关键点在于:Accept-Encoding:gzip,deflate,sdch。虽然有一些与分块相关的内容,但这并不影响这个特定问题。所以,浏览器正在请求mod_deflate启动,这打败了我在cgi脚本中仔细地吐出字节的计划。你可以在浏览器中更改它,但更明智的做法似乎是在服务器上为整个工作更改一次。也许这会有所帮助。

4
在PHP中,在不更改php.ini或拥有.htaccess文件的情况下,可以通过在脚本开头使用ob_end_flush()或ob_end_clean()来关闭运行时的输出缓冲。例如:
这样应该可以无缓冲输出:
<?php
@ob_end_clean();

for ($i = 0; $i < 5; $i++)
{
    echo "$i\n";
    flush();
    usleep(0.5e6);
}

如果 output_buffering 开启,无论是否调用 flush(),都会以缓冲方式输出(一次性输出):

<?php

for ($i = 0; $i < 5; $i++)
{
    echo "$i\n";
    flush();
    usleep(0.5e6);
}

尽管它的名字是ob_implicit_flush,但它在每次输出后隐式地调用的是flush()而不是ob_flush()。在关闭输出缓冲区之后,这在某些情况下非常方便:
<?php
@ob_end_clean(); // disable output buffer
ob_implicit_flush(); // call flush() automatically after every output

for ($i = 0; $i < 5; $i++)
{
    echo "$i\n";
    usleep(0.5e6);
}

这修复了PHP端的问题。可能还有其他与mod_deflate或类似的东西有关(请参考Ted Collins的答案),我观察到Firefox至少需要1024字节才能开始输出任何内容。

最佳答案,缺失的是 ob_implicit_flush()。 - Tengiz
ob_end_clean():删除缓冲区失败。没有可删除的缓冲区。 - Anton Duzenko
@AntonDuzenko 显然,当它没有激活时,现在会引发一个E_NOTICE级别的消息。这意味着你一开始就不需要它,但如果你想保持代码与其他需要它并且想要保留它的服务器兼容,只需在ob_end_clean的开头添加一个@符号来消除该通知。我已相应地编辑了答案。 - Pedro Gimeno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接