当时不太会搜索,找到的各种方法都弗成,每次都是延迟后输出全部结果,并不会一点一点输出。

代码差不多长这样:

<?phpob_start();for($i = 0; $i < 10; $i++) { echo "aaa:{$i}"; echo str_pad('',4096)."<br/>"; ob_flush(); flush(); sleep(1);}ob_end_flush();

实际运行结果是前端等待10秒后,一次性打印全部内容。

php显示不出来PHP及时输出为何一向掉败 Vue.js

最近深入学习了网络事理后,溘然想到这个问题,想着会不会是由于 TCP协议 里的 Nagle 算法 的缘故,于是就再次试了一下,结果依然是失落败。
不过现在我的搜索能力和以前大不相同,经由多番查找和实验,终于成功了。

这个实在和 Nagle 算法关系不大,毕竟网上给的例子很多都会在输出的末端加上一大堆空字符串,这远远超出了超出了窗口大小,而且延迟确认应答也有韶光限定,最大延迟0.5秒发送确认应答,很多操作系统设置为0.2秒旁边,换句话说,最多0.5秒,后端就会将数据发送给前端,从而实现稍带应答。
但现实情形是网页等待的韶光是所有 sleep 的总和,而且网页是一次性打印出所有内容的,因此和它无关。

真正的缘故原由是输出缓冲区,后端发送的数据只有等缓冲区满了才会真正发送给前端,而这个缓冲区多达四层,乃至可以更多。
只要个中一个缓冲区未满,数据就发送不出去,前端就不会立即看到结果,这便是我为何一贯失落败的缘故原由。
那么多层缓冲区,总有一个你没设置好,然后就被卡住了。

下面我们就来一个一个地占领这些难关。

1、PHP默认缓冲区(Default Output Layer)

除了 HTTP 首部外,不管你是用 print 还是用 echo 等打印内容,都会先存储在这个缓存区中,只有缓冲区满了,才会输出到下一个缓冲区中。

可以在 php.ini 配置中设置 output_buffering = Off 以关闭缓冲区,我试过用 ini_set 函数进行设置是无效的。

你也可以利用 ob_flush 逼迫刷新缓冲区。

2、SAPI Buffer

PHP-FPM 实现了 SAPI 接口,PHP 默认缓冲区输出的内容会被 PHP-FPM 进程缓存。

在 php.ini 配置中设置 implicit_flush = On 可以自动刷新缓冲区,这个同样无法用 ini_set 进行修正。

利用 flush 函数逼迫刷新缓冲区也可以起到同样的效果。

这里有一个地方要把稳,当 output_buffering = On 时,对 implicit_flush 进行修恰是无效的,只能利用 flush 方法逼迫刷新缓冲区。

3、Nginx 缓冲区

在 Nginx 配置文件中设置 fastcgi_buffering off 可以禁用缓冲区,也可以在 PHP 脚本中添加 header('X-Accel-Buffering: no') 禁用缓冲区。

4、浏览器缓存

到了这里,数据已经通过 HTTP 协议发送给了前端(浏览器),但浏览器并不会立即显示,其本身同样会缓存数据。
可以在掌握台中利用 curl -N url 进行测试。
或者在数据尾部添加一些空字符,强行填满缓冲区。

实际我测试的时候特殊奇怪,在命令行上利用 curl 是没有问题的,但是在 Chrome 浏览器中,访问时并不会立即显示内容,而这并非由于浏览器缓存,由于当我在程序中加入 ob_flush 后,浏览器是会立即打印内容的,而我所打印的内容只有三个字节,这浏览器缓存肯定是没有用到的。
这样看来 output_buffering = Off 并没有生效,可是如果你把这个配置去掉的话,那不管你加不加 ob_flush,浏览器都不会立即输出结果。
其余去掉 Accept-Encoding 头同样可以立即输出结果,确实奇怪。

实在大家也不用过于纠结,现实也不会有这么奇葩的需求,真要碰着了,多测试一下就行了。

User Output Layers

除了运用本身自带的缓存,我们也可以利用 ob_start 创建缓冲区。

多次调用 ob_start 会创建多个缓冲区,它们就像连在一起的漏斗,上一层慢了,就会把数据刷新到下一层。

flush 只能刷新最顶层的缓冲区,不管它是不是满的。

ob_end_flush 在刷新完顶层缓冲区后会关闭该缓冲区,这样多次调用后就可以把数据刷新到 SAPI 缓冲区。

一些框架所利用的模板措辞便是利用自定义的缓冲区,将数据网络起来,再利用 ob_get_contents 和 ob_end_clean 将数据从缓冲区中读取出来,末了才打印数据。

代码

末了我们来看下完全代码,方便须要的时候直接拷贝。

<?php// PHP输出缓冲区关闭// 配置 php.ini,这个 ini_set("output_buffering", "Off") 是无法修正的// output_buffering = Off;// 可用 ob_flush() 刷新// 关闭 SAPI 的缓冲区// 配置 php.ini,这里 ini_set('implicit_flush', 1) 设置无效// implicit_flush = On// 可用 flush() 刷新// 关闭nginx缓冲区header('X-Accel-Buffering: no');// 或在 Nginx 配置文件中设置 fastcgi_buffering offecho 'abc'; // 少于三个字符也弗成ob_flush(); // 在浏览器中运行要加这个// flush(); // 保险起见,最好 flush 一下sleep(3);echo 'b';// 测试// curl 可以禁用缓存,建议用这个测试,当然最好的办法是 cli+日志文件// curl -N http://php.test.com/test.php解释

在命令行CLI下运行是不会有缓冲区这个问题的,默认都是关闭的,除非用户主动创建。

切忌在生产环境里关闭缓冲区,做事器炸锅了我可不卖力。

以上结果在不同的PHP版本中可能会有不同的表现,以实际运行结果为准,下面是我的测试环境数据:

Docker版本:19.03.5

PHP版本(运行于Docker内):v7.4.6

Openresty版本(运行于Docker内):1.15.8.3

Chrome版本:87.0.4280.88(正式版本) (x86_64)

总结

这个我研究了良久,各种修正代码和配置,搞得我都想了,sun of .

末了,作为一个PHPer,我想说,PHP是最好的措辞,没有之一,哈哈。

喜好的朋友欢迎点赞,让更多的人看到。