php中ob函数缓冲机制深入理解_php实例_脚本之家

php 缓冲简介

其实我对php ob
系列印象还是很模糊,具体怎么玩的,还不是很了解,平时curd,确实对这些内容没有深入。作为phper
甚是惭愧。网上搜了一通,互相copy,代码运行不能出现作者所描述现象,本文良心出品,代码都是作者运行过。

当执行输出的时候,比如 echo,print。输出并没有立即送给 web server,
而是将数据写入 php buffer。php output_buffering
机制好处当然提升性能。其实 php 文件最终在浏览器上显示,走过3个缓冲阶段:
php buffer=》web server buffer=》browser buffer。 最后显示到浏览器

默认情况下,php buffer 是开启的,而且该 buffer 默认值是4096,即4
kb。你可以通过在php.ini配置文件中找到output_buffering配置。buffer是一个内存地址空间,Linux系统默认大小一般为4096(4kb),即一个内存页。主要用于存储速度不同步的设备或者优先级不同的设备之间传办理数据的区域。通过buffer,可以使进程这间的相互等待变少。这里说一个通俗一点的例子,你打开文本编辑器编辑一个文件的时候,你每输入一个字符,操作系统并不会立即把这个字符直接写入到磁盘,而是先写入到buffer,当写满了一个buffer的时候,才会把buffer中的数据写入磁盘,当然当调用内核函数flush()的时候,强制要求把buffer中的脏数据写回磁盘。

我的基础环境是nginx1.6.2+php5.3
做一个逐行输出,使用ob_flush时试了N种方法不起作用,比如下面的代码:

下面就php中ob函数缓冲机制通过文字说明加代码分析的形式给大家展示如下:

举个例子

<?php
echo "南无阿弥陀佛<br>";
header("content-type:text/html;charset='utf-8'");
echo "真善忍好!";
//output
//南无阿弥陀佛
//真善忍好

header()必须在任何实际输出之前调用,但是我们程序已经输出了,却正常运行。在看下面的代码:

<?php

echo "南无阿弥陀佛<br>";
ob_flush();
header("content-type:text/html;charset='utf-8'");
echo "真善忍好!";
//output
//南无阿弥陀佛
//Cannot modify header information - headers already sent by (output started at E:phptest.php:3)
//真善忍好

上面程序说明程序并没有立即输出,而当调用ob_flush
函数的时候才刷新缓冲,输出。

<?php
ob_start();
for(;;)
{
    echo “<br>…….”;
    ob_flush();
    flush();
    sleep(1);
}
?>

对于一个刚刚入门的php程序员来说,php缓冲区是几乎透明的。在他们心目中,一个echo
print_r
函数
,数据便会‘嗖’的一声飞到浏览器上,显示出来。我也一直如此单纯地认为。
其实,在技术的世界里,向来都是由简单到复杂,也许那些技术开发者开始单纯如你我,但是面对残酷的现实,不得不调整策略,以期提高机器运行效率,最后想到了那些让我们赞叹的idea。

ob_flush() 与 flush()

ob_flush() , flush() 函数php
手册上都有详细的说明,你可以去查阅一下。二者的区别是:

ob_flush() 是刷新PHP自身的缓冲区

flush()是 它是刷新WebServer
服务器的缓冲。输出到浏览器。但是会出现下面的情况:

  1. 个别web服务器程序,特别是Win32下的web服务器程序,在发送结果到浏览器之前,仍然会缓存脚本的输出,直到程序结束为止。
  2. 有些Apache的模块,比如mod_gzip,可能自己进行输出缓存,这将导致flush()函数产生的结果不会立即被发送到客户端浏览器。
  3. 甚至浏览器也会在显示之前,缓存接收到的内容。例如 Netscape
    浏览器会在接受到换行或 html 标记的开头之前缓存内容,并且在接受到
    </table> 标记之前,不会显示出整个表格。
  4. 一些版本的 Microsoft Internet Explorer
    只有当接受到的256个字节以后才开始显示该页面,所以必须发送一些额外的空格来让这些浏览器显示页面内容。

比如:

<?php
/**
Server:LightTPD/1.4.28 (Win32)
X-Powered-By:PHP/5.3.27
*/
echo '佛法无边'."<BR>";
ob_flush();
flush();
sleep(1);
echo '法轮常转';

//output

上面的代码 在 chrome 浏览器上面
是一行一行的输出,在ie系列的浏览器则是全部输出。其实就是上面的
第四条一些浏览器只有当接收256个字符才开始显示。把上面的代码改成下面形式:

<?php
/**
Server:LightTPD/1.4.28 (Win32)
X-Powered-By:PHP/5.3.27
*/
echo str_pad('',240)."n"; 
echo '佛法无边'."<BR>";
ob_flush();
flush();
sleep(1);
echo '法轮常转';

//output

这样在ie下面就会一行一行输出,因为超过256个字符。

谷歌了不少的写法都不行,所以问题应该出在了环境配置上而不是使用方法上。
话说还是stackoverflow给力,搜索“php flush not
working”找到了一个正确的解决方法:
检查nginx配置文件(nginx.conf),禁用nginx的buffering:

说到缓冲,也就是buffer,这里必须要和缓存做一下比较,单纯地比较定义是无意义的,莫不如看看它们做什么。缓存解决的是如何快速查找利用数据,节省cpu消耗问题,而缓冲解决的是高速cpu与低速I/O设备不匹配的问题。

ob 其他函数说明

1.ob_end_flush 与 ob_end_clean**

end
的顾名思义就结束,关闭缓冲区,都是关闭输出缓冲,一个是输出缓冲区,一个是清除。比如

<?php
/**
Server:LightTPD/1.4.28 (Win32)
X-Powered-By:PHP/5.3.27
*/
echo 'before';
ob_end_clean();
echo str_pad('',4096)."n";
for ($i=10; $i>0; $i--) 
{ 
    echo $i;
    sleep(1);
}

上述代码是一下输出全部内容,而不是一个一个输出。ob_end_clean()
不是关闭了缓冲了?怎么不是一个一个输出呢,其实我们上面也说了,php
不是直接输出给浏览器,而是 web server。 虽然php 没有了 缓冲。但是web
server 还是有的。所以下面代码:

/**
Server:LightTPD/1.4.28 (Win32)
X-Powered-By:PHP/5.3.27
*/
echo 'before';
ob_end_clean();
echo str_pad('',4096)."n";
for ($i=10; $i>0; $i--) 
{ 
    flush();
    echo $i;
    sleep(1);
}

加上flush(),就会一行一行输出。 如果把ob_end_clean 换成 ob_end_flush 
会把 before 输出来。

其他函数 可参考手册,比较简单。

proxy_buffering off;
gzip off;
fastcgi_keep_conn on;

再说下本文的另一个主角,ob函数,ob是output_buffering的简写。既然ob函数是php扩展函数,那么ob函数主要操作的也就是php
buffer
了。

总结

php 脚本到浏览器,要经过  php buffer=》web server buffer=》browser
buffer。 最后显示到浏览器。 缺一不可。 所以我们要 ob_flush 和 
flush 以及加上  echo str_pad(”,4096) 才能调试出你想要的效果。

要注意最后这句fastcgi的哦~~
检查php.ini,禁用buffering:

简单说完本文两个主角,我们还必须回归开头的主题,echo
print_r
函数输出的数据是怎么到达浏览器让用户看到的呢?实际上的历程是这样的:

output_buffering = off

复制代码 代码如下:echo、print_r=>php
output_buffering=>webServer buffer=>browser buffer=>browser
display

注意这句配置不能通过ini_set()函数动态在程序中设置,这在php官方手册中有说明:
the output_buffering setting is PHP_INI_PERDIR therefore it may not
be set using ini_set()
经过上面两步的配置(nginx.conf和php.ini)后,重启nginx就可以了,再次测试文章开头的代码,成功逐行输出

我们可以清楚地看到,从echo、print_r函数到发送信息给客户端经历了两个缓冲区,在客户端还经历了一个浏览器缓冲区。我们本文主要讨论的是php
output_buffering

补充:PHP flush()与ob_flush()的区别

未使用ob函数时缓冲区的使用情况

buffer —- flush()
 
buffer是一个内存地址空间,Linux系统默认大小一般为4096(1kb),即一个内存页。主要用于存储速度不同步的设备或者优先级不同的
设备之间传办理数据的区域。通过buffer,可以使进程这间的相互等待变少。这里说一个通俗一点的例子,你打开文本编辑器编辑一个文件的时候,你每输入
一个字符,操作系统并不会立即把这个字符直接写入到磁盘,而是先写入到buffer,当写满了一个buffer的时候,才会把buffer中的数据写入磁
盘,当然当调用内核函数flush()的时候,强制要求把buffer中的脏数据写回磁盘。
同样的道理,当执行echo,print的时候,输出并没有立即通过tcp传给客户端浏览器显示,
而是将数据写入php buffer。php output_buffering机制,意味在tcp
buffer之前,建立了一新的队列,数据必须经过该队列。当一个php
buffer写满的时候,脚本进程会将php
buffer中的输出数据交给系统内核交由tcp传给浏览器显示。所以,数据会依次写到这几个地方echo/pring
-> php buffer -> tcp buffer -> browser

我们的代码很多时候是根本不使用ob函数的,那么它们使用缓冲区了吗?这要看php设置情况。缓冲区是通过php.ini中的output_buffering变量控制的。其默认值是off,可以设置为on来打开buffer。打来buffer后,即便程序中没有用ob函数,实际上代码也是使用了缓冲区的。另外,不管php.ini中output_buffering的设置,cli模式下的php始终默认是关闭的。

php output_buffering — ob_flush()

为什么要是缓冲区呢?简单来说,高速的cpu早早处理完自己的数据,想通过线路传递给用户,但是线路太窄了,一下输送不过去。如果引入缓冲区,cpu可以将快速将生成的数据放入缓冲区,然后自己哪儿凉快儿哪儿呆着这歇着去了。缓冲区根据指令适时将数据输出。这个样就合理解决了高速cpu与低速I/O设备的矛盾了。

默认情况下,php
buffer是开启的,而且该buffer默认值是4096,即1kb。你可以通过在php.ini配置文件中找到output_buffering配置.当echo,print等输出用户数据的时候,输出数据都会写入到php
output_buffering中,直到output_buffering写满,会将这些数据通过tcp传送给浏览器显示。你也可以通过
ob_start()手动激活php
output_buffering机制,使得即便输出超过了1kb数据,也不真的把数据交给tcp传给浏览器,因为ob_start()将php
buffer空间设置到了足够大
。只有直到脚本结束,或者调用ob_end_flush函数,才会把数据发送给客户端浏览器。

缓冲区的数据什么时候输出呢?1,当缓冲区满了的时候,缓冲是有容量大小的,到达极限则会自动输出内容。2,脚本执行完毕。很多小程序输出内容没那么多,总不能等到缓冲区满了再输出吧~这一点再自然不过。

这两个函数的使用怕是很多人最迷惑的一个问题,手册上对两个函数的解释也语焉不详,没有明确的指出它们的区别,似乎二者的功能都是刷新输出缓存。但在我们文章一开始的代码中如果讲fush()替换成ob_flush(),程序就再不能正确执行了。显然,它们是有区别的,否则也手册中直接说明其中一个是另外一个函数的别名即可了,没必要分别说明。那么它们的区别到底是什么呢?

使用ob函数时缓冲区的使用情况

在没有开启缓存时,脚本输出的内容都在服务器端处于等待输出的状态
,flush()可以将等待输出的内容立即发送到客户端。

复制代码 代码如下: ob_start()

开启缓存后,脚本输出的内容存入了输出缓存中
,这时没有处于等待输出状态的内容,你直接使用flush()不会向客户端发出任何内容。而
ob_flush()的作用就是将本来存在输出缓存中的内容取出来,设置为等待输出状态,但不会直接发送到客户端
,这时你就需要先使用
ob_flush()再使用flush(),客户端才能立即获得脚本的输出。

打开输出缓冲。这个函数是我们调用最多的一个函数之一。在output_buffering设置为on或者x
k的情况下,这个函数与其说是打开输出缓冲,还不如说将输出缓冲扩充到很大。当然在output_buffering设置为off的条件下,ob_start会起到打开buffer的作用。ob_start()还可以传递一个可选参数
output_callback 函数,php官方手册有详细说明。

一. flush和ob_flush的正确顺序,正确应是,先ob_flush再flush,如下: ob_flush();
flush();
如果Web服务器的操作系统是windows系统,那顺序颠倒或者不使用ob_flush()也不会出现问题。[有待求证
] 但是在Linux系统上就无法刷新输出缓冲。

复制代码 代码如下:ob_get_contents()

output buffering函数 1.bool ob_start ([ callback $output_callback [, int $chunk_size
[, bool $erase ]]] )
激活output_buffering机制。一旦激活,脚本输出不再直接出给浏览器,而是先暂时写入php
buffer内存区域。
php默认开启output_buffering机制,只不过,通过调用ob_start()函数据output_buffering值扩展到足够

。也可以指定$chunk_size来指定output_buffering的值。$chunk_size默认值是0,表示直到脚本运行结束,php
buffer中的数据才会发送到浏览器。如果你设置了$chunk_size的大小
,则表示只要buffer中数据长度达到了该值,就会将buffer中
的数据发送给浏览器。
当然,你可以通过指定$ouput_callback,来处理buffer中的数据。比如函数ob_gzhandler,将buffer中的数据压缩后再传送给浏览器。
第三个参数:是否擦除缓存,可选,默认是true,如果设置为false,则在脚本执行结束前,缓存都不会被清除。
2.ob_get_contents
获取一份php
buffer中的数据拷贝。值得注意的是,你应该在ob_end_clean()函数调用前调用该函数,否则ob_get_contents()返回一个空字符中。

只是得到输出缓冲区的内容,但不清除它。

可以使用ob_get_contents()以字符串形式获取服务端缓存的数据,
使用ob_end_flush()则会输出被缓存起来的数据,并关闭缓存。
而使用ob_end_clean()则会静默的清除服务端缓存的数据,而不会有任何数据或其他行为。
服务端的缓存是堆叠起来的,也就是说你在开启了ob_start()后,关闭之前,在其内部还
可以开启另外一个缓存ob_start()。

复制代码 代码如下:ob_end_clean

不过你也要务必保证关闭缓存的操作和开启缓存的操作数量一样多。
ob_start()
可以指定一个回调函数来处理缓存数据,如果一个ob_start()内部嵌套了另一个ob_start(),我们假定,外层的ob_start(),编号是A,内层的ob_start()编号是B,它们各自制定了一个回调函数分别是functionA和functionB,那么在缓存B中的数据输出时,它会先辈funcitonB回调函数处理,再交给外层的functionA回调函数处理,之后才能输出到客户端。

这两个函数从字面意思上就可以看出其区别。前者清除缓冲区内容并且关闭,后者仅仅是做清除工作。需要注意的是,使用了这两个函数,在前面使用了echo、print_r等函数不会输出内容。

另外,手册说,对于某些web服务器,比如apache,在使用回调函数有可能会改变程序当前的工作目录,解决方法是在回调函数中自行手动把工作目录修改回来,用chdir函数,这点似乎不常遇到,遇到的时候记得去查手册吧。

笔者曾经试图通过print_r打印出ob_get_contents()的内容,然后调用ob_clean()清除缓冲区,以免影响后面对缓冲区的操作,屡屡失败。仔细想想,print_r的内容再次写入缓冲区,而后面做了ob_clean()的操作,自然不会有任何输出。在ob_clean操作之前调用ob_flush()函数便可达到预想的效果。

3.ob_end_flush与ob_end_clean 这二个函数有点相似,都会关闭ouptu_buffering机制。但不同的是,ob_end_flush只是把php
buffer中的数据冲(flush/send)到客户端浏览器,而ob_clean_clean将php
bufeer中的数据清空(erase),但不发送给客户端浏览器。

复制代码 代码如下:ob_flush

ob_end_flush调用之前 ,php
buffer中的数据依然存在,ob_get_contents()依然可以获取php
buffer中的数据拷贝。

ob_flush()送出缓冲区的内容并且丢弃内容。因而在此函数之前最好采用ob_get_contents刷出服务器端缓冲,并且发往客户端。因而从流程上来说,应该是先调用ob_flush()而后再调用flush函数。

而ob_end_flush()调用之后
ob_get_contents()取到的是空字符串,同时浏览器也接收不到输出,即没有任何输出。

另外说明下再Apache buffer flush()的工作原理:在apache
module
的sapi下,flush会通过调用sapi_module()的flush成员函数指针,间接使用apache的api::ap_rflush刷新apache的输出缓冲区。当然apache其他模块比如mod_gzip可能改变这个动作的结果,可能自己进行输出缓冲区,这将导致flush()函数产生的结果不会立即被送到客户端浏览器。

可以使用ob_get_contents()以字符串形式获取服务端缓存的数据,使用ob_end_flush()则会输出被缓存起来的数据,并关闭缓存。
而使用ob_end_clean()则会静默的清除服务端缓存的数据,而不会有任何数据或其他行为。
服务端的缓存是堆叠起来的,也就是说你在开启了ob_start()后,关闭之前,在其内部还可以开启另外一个缓存ob_start()。不过你也要务必保证关闭缓存的操作和开启缓存的操作数量一样多。
ob_start()
可以指定一个回调函数来处理缓存数据,如果一个ob_start()内部嵌套了另一个ob_start(),我们假定,外层的ob_start(),编号是A,内层的ob_start()编号是B,它们各自制定了一个回调函数分别是functionA和functionB,那么在缓存B中的数据输出时,它会先辈funcitonB回调函数处理,再交给外层的functionA回调函数处理,之后才能输出到客户端。

复制代码 代码如下:ob_get_clean()

如果你已经熟练掌握ob_get_contents()ob_clean(),那这个函数就很简单了。因为它是前两者的结合体。它主要是得到当前缓冲区的内容并删除当前输出缓冲区。

ob函数还有很多,但大部分用法比较简单,理解较为容易。大家可以参照php手册
,里面会有详细的解释。本文列出了笔者开始并不是很理解的一些函数,当然今后还会有新的问题出现,想到问题并且解决问题,生活的乐趣也许就在此处吧。

以上内容就是本文针对php中ob函数缓冲机制深入理解,希望对大家今后学习有所帮助。

发表评论

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