PHP输出缓冲(output buffer)初级理解和应用

声明:不同的PHP版本,操作系统环境,Webserver, 参数默认值及配置可能会有差异。本文基于的环境如下:
PHP version: 7.2.8; Nginx version 1.14.0
Mac OS version: 10.10.5; Chrome version: 69.0;
Safari version: 8.0.8

第一部分 基础概念

计算机缓冲区(buffer)
计算机中暂时存放输出或输入信息的内存区域。缓和高速部件和低速部件之间通信速度不匹配的矛盾。缓冲(buffer)和缓存(cache)之间还是有本质区别的【点击查看区别】。

PHP输出缓冲区
输出缓冲区顾名思义是输出信息暂时存放的内存区域,通过ob_*系列函数来控制输出缓冲区。当php脚本执行结束(会自动调用ob_flush())或强制刷新(手动调用ob_fush())缓冲区后,才会把数据发送给Nginx fastcgi客户端。当然PHP还有其他的缓冲区,比如字符串缓冲区finfo::buffer

PHP输出缓冲区默认是关闭的,可以在php.ini中开启,生产环境官方建议大小是4096字节。开启后对所有php页面都生效。

output_buffering = 4096
// 其他值说明
; On = Enabled and buffer is unlimited. (小心使用)
; Off = Disabled
; Integer = Enables the buffer and sets its maximum size in bytes.
; Note: This directive is hardcoded to Off for the CLI SAPI

另外一种在页面中单独开启缓冲区的办法是ob_start()函数。

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

ob_start()有三个参数,$chunk_size是用来设置缓冲区大小,可以设置0-4096,默认是0表示大小不限。

PHP-FPM缓冲区(暂时这么认为,如果读者有更好的建议,欢迎留言)
参考《PHP output buffer – bigpipe基础》。这篇文章提到认为是fpm缓冲区,但是官网没有明确说明,测试验证确实有8K的缓冲,通过flush()刷新。flush()函数严格来说, 这个只有在PHP做为Apache的Module安装的时候, 才有实际作用,它是刷新WebServer(可以认为特指Apache)的缓冲区。参考鸟哥文章深入理解flush和ob_flush区别。所以flush()并不能刷新Nginx fastcgi缓冲区。

Nginx缓冲区
Nginx默认不会实时把php-fpm响应的数据返回给客户端,而是暂存在Nginx缓冲区中。当php脚本执行结束(自动调用flush())或强制刷新(手动flush())缓冲区后,才会把数据发送给客户端。Nginx buffer有fastcgi_buffering和porxy_buffering两种。Nginx通过fastcgi_buffer*相关指令来控制fastcgi_buffering缓冲区。通过porxy_buffer*指令来控制porxy_buffering。

浏览器缓冲区
浏览器默认不会实时显示从Nginx返回的数据,而是把接受到的数据暂存在浏览器缓冲区中,当缓冲区满后,才会开始显示。不同的浏览器缓冲区大小不同。实际测试发现Mac 下chrome和safari都需要输出1024字节。没有找到刷新缓冲区的办法,可以通过发送额外的空格来解决。

或者通过curl来请求,通过–no-buffer来禁用curl buffer。

curl 'niliu.me' --no_buffer

把上面三个部分串起来,完整的流程如下图【下图只是自己理解使用,仅供参考】:

第二部分 浏览器实时显示PHP程序输出

一、PHP输出缓冲区和Nginx缓冲区都关闭。
0、php.ini关闭PHP输出缓冲区。
output_buffering = Off
1、关闭nginx缓冲区
方式一、nginx.conf中关闭Nginx fastcgi buffer缓冲区。
fastcgi_buffering off;
方式二、PHP文件中起始行增加头信息。
header(‘X-Accel-Buffering: no’);
2、浏览器请求脚本,先输出hello, 过2秒输出php。

header('X-Accel-Buffering: no');

echo str_pad('hello', 1024);
// 测试发现调用flush()后,chrome不需要向浏览器发送额外的空格,浏览器也可以实时显示
flush();
// 不加flush(), 输出8K内容也是可以的。
// echo str_pad('hello', 1028 * 8);
sleep(2);
echo 'php';
die;

二、PHP输出缓冲区关闭,Nginx缓冲区开启。
0、php.ini关闭PHP输出缓冲区。
output_buffering = Off
1、nginx.conf中开启fastcgi 缓冲,buffer size是默认大小4K。(当然也可以在PHP脚本中通过header(‘X-Accel-Buffering: yes’);开启)
fastcgi_buffering on;
fastcgi_buffer_size 4k;
fastcgi_buffers 8 4k;
2、浏览器请求脚本,先输出hello, 过2秒输出php。

echo str_pad('hello', 1024 * 4);
flush();
sleep(2);
echo 'php';
die;

三、PHP输出缓冲区开启,Nginx缓冲区关闭。
0、php.ini开启PHP输出缓冲区。
output_buffering = 4096
1、nginx.conf中开启fastcgi 缓冲,buffer size是默认大小4K。
fastcgi_buffering off;
2、浏览器请求脚本,先输出hello, 过2秒输出php。

echo str_pad('hello', 1024);
// 要通过ob_flush()把输出缓冲区的内容冲出。
ob_flush();
flush();
sleep(2);
echo 'php';
die;

四、PHP开启多级输出缓冲区,Nginx缓冲区关闭。
0、php.ini开启PHP输出缓冲区。
output_buffering = 4096
1、nginx.conf中开启fastcgi 缓冲,buffer size是默认大小4K。
fastcgi_buffering off;
2、浏览器请求脚本,先输出hello, 过2秒输出php。

// php.ini中开启了缓冲区,在通过ob_start()开启不限制大小的缓冲区,需要通过ob_end_flush()冲出缓冲区内部嵌套的缓冲,在通过ob_flush()冲出到Nginx fastcgi
ob_start();
echo str_pad('hello', 1024);

ob_end_flush();
ob_flush();
flush();
sleep(2);
echo 'php';
die;

注:有些人提到php.ini中开启了PHP输出缓冲区,ob_start()才生效,应该是一个误区,之间没有依赖关系。

五、PHP输出缓冲区开启,Nginx缓冲区开启。
0、php.ini开启PHP输出缓冲区。
output_buffering = 4096
1、nginx.conf中开启fastcgi 缓冲,buffer size是默认大小4K。(当然也可以在PHP脚本中通过header(‘X-Accel-Buffering: yes’);开启)
fastcgi_buffering on;
fastcgi_buffer_size 4k;
fastcgi_buffers 8 4k;
2、浏览器请求脚本,先输出hello, 过2秒输出php。

// nginx fastcgi缓冲区大小是4K,需要发送额外4K空格
echo str_pad('hello', 1024 * 4);

ob_flush();
flush();
sleep(2);
echo 'php';
die;

完整写法,参考php手册flush()

if (ob_get_level() == 0) {
        ob_start();
}

for ($i = 0; $i<10; $i++){
        echo "Line to show.";
        echo str_pad('',4096)."\n";    

        ob_flush();
        flush();
        sleep(2);
}

echo "Done.";
ob_end_flush();

问题:
很多人提到用下面的方式发送额外的空格到浏览器, 但是hello php还是同出显示。

echo str_repeat(' ', 4096); // 注意引号之间有空格

ob_flush();
flush();
sleep(2);
echo 'php';
die;

解决:需要在输出空格之前,输出一个非空字符。比如echo 1。怀疑空格是被Nginx或浏览器过滤了。

第三部分 实际应用

数据下载:参考下篇博文《PHP实现浏览器文件下载》
文件下载:整理待定。

参考:

从php到浏览器的缓存机制,不得不看
深入理解PHP输出缓冲区
php+nginx实时输出的问题
信海龙:从php缓冲区说起

PHP输出缓冲(output buffer)初级理解和应用》上有2条评论

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注