PHP的flush函数无法正常工作

17
<?php
for($i=0;$i<20;$i++)
{
    echo 'printing...<br />';
    ob_flush();
    flush();

    usleep(300000);
}

?>

包含代码的网址:http://domainsoutlook.com/sandbox/delayed.php

我有一台专用服务器,所以我可以进行更改。我正在运行Apache和Nginx作为代理服务器。


5
Apache和nginx?我认为nginx就足够了。 - jwueller
你期望发生什么? - John Carter
在使用循环之前,需要使用ob_start()开始一个输出缓冲区,并且应该在flush()之后使用ob_flush() - RobertPitt
@robertpitt,您能否编辑代码并回答问题,以便我测试并选择您的答案作为正确答案。谢谢。 - Speedy Wap
PHP flush/ob_flush not working 的副本 - netcoder
14个回答

48

这就是我找到的结果:

在Apache的mod_gzip或Nginx的gzip情况下,Flush不起作用,因为它压缩内容,必须缓冲内容以进行压缩。任何类型的Web服务器压缩都会影响此操作。简而言之,在服务器端,我们需要禁用gzip并减小fastcgi缓冲区大小。因此:

  • 在php.ini中:

    . output_buffering = Off

    . zlib.output_compression = Off

  • 在nginx.conf中:

    . gzip off;

    . proxy_buffering off;

如果您无法访问php.ini,请使用以下行:

  • @ini_set('zlib.output_compression',0);

  • @ini_set('implicit_flush',1);

  • @ob_end_clean();

  • set_time_limit(0);

最后,如果有,请注释掉下面的代码:

  • ob_start('ob_gzhandler');

  • ob_flush();

PHP测试代码:

ob_implicit_flush(1);

for($i=0; $i<10; $i++){
    echo $i;

    //this is for the buffer achieve the minimum size in order to flush data
    echo str_repeat(' ',1024*64);

    sleep(1);
}

15
“回响64k无意义的空格”是最后起作用的事情。谢谢罗杰。 - humanityANDpeace
.proxy_buffering off; 是我的配置问题。谢谢! - Noam
使用php7.2fpm时,@ob_end_clean最终解决了我的问题。 - Jeff Beagley
1
我花了5个小时才找到SSE FLUSH无法工作的问题所在... 这是为了缓冲区达到最小大小以便刷新数据。哦,该死!... 看起来还没有绕过它的方法:https://stackoverflow.com/q/57207571/1066234 - Avatar
注意:echo str_repeat(' ', 4096); 似乎足够了。https://www.php.net/manual/en/function.flush.php#51679 - Avatar
显示剩余2条评论

13

你正在使用 ob_flush 却没有使用 ob_start,因此对其进行刷新时没有任何内容可供刷新。

此外,这也取决于 Web 服务器和代理及其设置。

您应该禁用 Nginx 的缓冲(在配置文件中添加 proxy_buffering off; 并重新启动 Nginx)。

另外,请检查您的 php.ini 文件是否包含 output_buffering = Offzlib.output_compression = Off


通常nginx配置文件在哪里,我可以在哪里添加这些值? - Speedy Wap
我是否应该在HTTP核心模块中添加这行代码来配置Nginx? - Speedy Wap
4
我已将proxy_buffering = off,zlib.output_compression = off和proxy_buffering off设置为关闭,但仍无法正常工作。请问能否提供代码或其他信息帮我解决这个问题? - Speedy Wap
在这种情况下,您应该看看是否真的需要“动态”打印,并尝试其他方法来实现(例如AJAX)。 - schnaader

12

主PHP文件;

<?php
header('Content-Type: text/HTML; charset=utf-8');
header( 'Content-Encoding: none; ' );//disable apache compressed
session_start();
ob_end_flush();
ob_start();
set_time_limit(0);
error_reporting(0);

..... bla bla

for(each)........
{
   bla bla..
    echo "<br>>>>".$i."<<<br>";
    ob_flush();
    flush(); //ie working must

}
?>

它正在工作中..


1
Content-Encoding对我在nginx上起了作用 - 而不必为整个服务器关闭它。谢谢Emin! - Redzarf
2
设置 Content-Encoding 最终使刷新在 Firefox 上运行,但某种方式 Chrome 仍然期望压缩响应,因此它抛出了一个 ERR_CONTENT_DECODING_FAILED 错误。 - Jelle De Loecker

5
你需要填充缓冲区,以便将其刷新到浏览器。在 echo 后使用此方法。
echo str_pad('',4096)."\n";

完整代码:

完整的代码:

<?php
     if (ob_get_level() == 0) ob_start();

     for( $i=0 ; $i<20 ; $i++) {
        echo 'printing...<br />';
        echo str_pad('',4096)."\n";

        ob_flush();
        flush();

        usleep(300000);
     }
     ob_end_flush();
?>

太棒了!这么简单的事情解决了我的问题,但我在其他地方没有找到这个解决方案或要求。谢谢! - Cosworth66

4
php.ini 文件中:
output_buffering = Off
zlib.output_compression = Off

nginx.conf 文件中:
fastcgi_keep_conn on; # < solution
proxy_buffering off;
gzip off;

fastcgi_keep_conn on 有帮助!谢谢! - artnikpro

3
阅读后,似乎这是一个非常难解决的问题,我找到唯一(肮脏)的方法是写一些无用的内容输出来填充 ≠ 缓冲区。
没有SSL的情况下
没有 output_buffering,需要使用 flush。 可以降低 Nginx 缓冲区大小,直到与 PHP 标头大小相同。 有 output_buffering,需要添加 ob_flush 以达到上述相同行为。
有SSL的情况下
SSL 还有另一个缓冲区,NGX_SSL_BUFSIZE 在 Nginx 编译中是固定的。
以下是我的 test.php 文件(通过 ?size=... 调用它以更改循环中的空格书写)。
<!DOCTYPE html>
<html>
<head></head>
<body>
<?php
$vars = array('output_buffering', 'zlib.output_compression');
print('<p>');
foreach ($vars as $var) {
  print("$var : ");
  var_dump(ini_get($var));
  print('<br />');
}
print("ob_get_level() : " .ob_get_level());
print('</p>');
if (ob_get_level()) {
  $bytes = ob_get_length();
  ob_flush();
}

$nb_iterations = !empty($_GET['nb']) ? max(2, (int) $_GET['nb']) : 5;
$size = !empty($_GET['size']) ? $_GET['size'] : 0;

for ($i = 1; $i < $nb_iterations; $i++) {
  sleep(1);
  print(str_repeat(' ', 1024 * $size ));
  print("<p>wait $i s</p>");
  if (ob_get_level()) {
    $bytes += ob_get_length();
    print($bytes + strlen($bytes));
    ob_flush(); // this is working, results aren't depending on output_buffering value
  }
  flush(); // this is needed  
}
?>
</body>
</html>

我可以设置的最低配置为

location ~ ^/test.php$ {
  gzip off;
  fastcgi_pass   unix:/var/run/php5-fpm/ssl.socket;
  fastcgi_param   QUERY_STRING            $query_string;  
  fastcgi_param   REQUEST_METHOD          $request_method;
  fastcgi_param   SCRIPT_FILENAME         $request_filename;   
  # if too low => upstream sent too big header while reading response header from upstream
  fastcgi_buffer_size 128; 
  fastcgi_buffers 2 128;  
  fastcgi_busy_buffers_size 128;
}

1
另一个可能的原因是mod_security。它似乎有自己的缓冲区。因此,如果您正在使用它,您将不得不设置:

SecResponseBodyAccess Off

这是一种有点不太正规的解决方法,但目前这是我唯一让它工作的方法。


1

我想补充一下Roger的回答。

如果你正在使用Apache2中的FastCGI php5-fpm模块,你还必须确保在Apache2配置中添加-flush参数。

例如:

<IfModule mod_fastcgi.c>
...
        FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -flush -socket /tmp/php5-fpm.sock -idle-timeout 480 -pass-header Authorization
</IfModule>

1

使用以下代码检查您的服务器API:

echo phpinfo();

如果您找到了您的服务器API

Server API :  CGI/FastCGI

如果是在CentOS系统中,则需要在"/etc/httpd/conf.d/fcgid.conf"文件中添加此行。

OutputBufferSize 0

为了测试,请重启您的Apache服务器并尝试以下代码

ob_start();
for($i = 0; $i < 10; $i ++) {
    echo $i;
    echo '<br />';
    flush();
    ob_flush();
    sleep(1);
}

这就是我要找的!谢谢 @gsm!我阅读了关于这个问题的所有答案/主题/博客,而我所需要的就是在我的Apache配置文件中将缓冲区大小设置为零。特别是如果你正在Windows上运行,并且使用fcgid_module,你应该在设置fcgid_module的httpd conf文件中添加FcgidOutputBufferSize 0 - Zoltán Kurgya

0

我只能通过添加session_write_close();来刷新。

if (ob_get_level() == 0)
{
    if(!ob_start("ob_gzhandler"))ob_start();
}               
echo ('bla bla bla');

$ans=ob_get_contents();
ob_end_clean();

header('Connection: close');
header('Content-Length: '.strlen($ans));
header('Status: 200');

echo $ans;

session_write_close();
ob_flush();
flush();

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