php缓冲 output_buffering和ob_start使用介绍


Posted in PHP onJanuary 30, 2014

buffer

buffer是一个内存地址空间,Linux系统默认大小一般为4096(4kb),即一个内存页。主要用于存储速度不同步的设备或者优先级不同的设备之间传办理数据的区域。通过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

php output_buffering

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

<?php
for ($i = 0; $i < 10; $i++) {
    echo $i . '<br/>';
    <a href="http://www.php.net/sleep">sleep</a>($i + 1);   //
}
?>

现象:不是每隔几秒就会有间断性输出,而是直到响应结束,才能看一次性看到输出,在等待服务器脚本处理结束之前,浏览器界面一直保持空白。这是因为,数据量太小,php output_buffering没有写满。写数据的顺序,依次是echo->php buffer->tcp buffer->browser
2.当output_buffering=0,并且输出较少数据(少于一个buffer)

<?php
//通过ini_set('output_buffering', 0)并不生效
//应该编辑/etc/php.ini,设置output_buffering=0禁用output buffering机制
//ini_set('output_buffering', 0);   //彻底禁用output buffering功能
for ($i = 0; $i < 10; $i++) {
    echo $i . '<br/>';
    <a href="http://www.php.net/flush">flush</a>();  //通知操作系统底层,尽快把数据给客户端浏览器
    <a href="http://www.php.net/sleep">sleep</a>($i + 1);   //
}
?>

现象:与刚才显示并不一致,禁用了php buffering机制之后,在浏览器可以断断续续看到间断性输出,不必等到脚本执行完毕才看到输出。这是因为,数据没有在php output_buffering中停留。写数据的顺序依次是echo->tcp buffer->browser
3.当output_buffering=4096.,输出数据大于一个buffer,不调用ob_start()
创建一个4kb大小的文件
$dd if=/dev/zero of=f4096 bs=4096 count=1

<?php
for ($i = 0; $i < 10; $i++) {
    echo <a href="http://www.php.net/file_get_contents">file_get_contents</a>('./f4096') . $i . '<br/>';
    <a href="http://www.php.net/sleep">sleep</a>($i +1);
}
?>

现象:响应还没结束(http连接没有关闭),断断续续可以看到间断性输出,浏览器界面不会一直保持空白。尽管启用了php output_buffering机制,但依然会间断性输出,而不是一次性输出,是因为output_buffering空间不够用。每写满一个php buffering,数据就会发送到客户端浏览器。 4.当output_buffering=4096, 输出数据大于一个tcp buffer, 调用ob_start()

<?php
<a href="http://www.php.net/ob_start">ob_start</a>();   //开启php buffer
for ($i = 0; $i < 10; $i++) {
    echo <a href="http://www.php.net/file_get_contents">file_get_contents</a>('./f4096') . $i . '<br/>';
    <a href="http://www.php.net/sleep">sleep</a>($i + 1);
}
<a href="http://www.php.net/ob_end_flush">ob_end_flush</a>();
?>

现象:直到服务端脚本处理完成,响应结束,才看到完整输,输出间隔时间很短,以至你感受不到停顿。在输出之前,浏览器一直保持着空白界面,等待服务端数据。这是因为,php一旦调用了ob_start()函数,它会将php buffer扩展到足够大,直到ob_end_flush函数调用或者脚本运行结速才发送php buffer中的数据到客户端浏览器。
tcpdump观察
在这里,我们通过tcpdump监控一下tcp报文,来观察一下使用ob_start()和没有使用它的一个区别。
1.没有使用ob_start()
12:30:21.499528 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . ack 485 win 6432 12:30:21.500127 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 1:2921(2920) ack 485 win 6432 12:30:21.501000 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 2921:7301(4380) ack 485 win 6432 12:30:21.501868 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 7301:8412(1111) ack 485 win 643 12:30:24.502340 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 8412:14252(5840) ack 485 win 6432 12:30:24.503214 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 14252:15712(1460) ack 485 win 6432 12:30:24.503217 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 15712:16624(912) ack 485 win 6432   12:30:31.505934 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 16624:23924(7300) ack 485 win 6432 12:30:31.506839 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 23924:24836(912) ack 485 win 6432 12:30:42.508871 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 24836:32136(7300) ack 485 win 6432 12:30:42.509744 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 32136:33048(912) ack 485 win 6432 12:30:57.512137 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . 33048:40348(7300) ack 485 win 6432 12:30:57.513016 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 40348:41260(912) ack 485 win 6432 12:31:06.513912 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: P 41260:41265(5) ack 485 win 6432 12:31:06.514012 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: F 41265:41265(0) ack 485 win 6432 12:31:06.514361 IP 192.168.0.8.webcache > 192.168.0.28.cymtec-port: . ack 486 win 6432
2.使用了ob_start()
12:36:06.542244 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . ack 485 win 6432 12:36:51.559128 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 1:2921(2920) ack 485 win 6432 12:36:51.559996 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 2921:7301(4380) ack 485 win 6432 12:36:51.560866 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 7301:11681(4380) ack 485 win 6432 12:36:51.561612 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 11681:16061(4380) ack 485 win 6432 12:36:51.561852 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 16061:20441(4380) ack 485 win 6432 12:36:51.562479 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 20441:24821(4380) ack 485 win 6432 12:36:51.562743 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 24821:29201(4380) ack 485 win 6432 12:36:51.562996 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 29201:33581(4380) ack 485 win 6432 12:36:51.563344 IP 192.168.0.8.webcache > 192.168.0.28.noagent: P 33581:35041(1460) ack 485 win 6432 12:36:51.563514 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 35041:36501(1460) ack 485 win 6432 12:36:51.563518 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 36501:37961(1460) ack 485 win 6432 12:36:51.563523 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 37961:39421(1460) ack 485 win 6432 12:36:51.563526 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . 39421:40881(1460) ack 485 win 6432 12:36:51.563529 IP 192.168.0.8.webcache > 192.168.0.28.noagent: FP 40881:41233(352) ack 485 win 6432 12:36:51.570364 IP 192.168.0.8.webcache > 192.168.0.28.noagent: . ack 486 win 6432
通过上面的对比,我们可以看到,数据报文的时间间隔明显不一样。没有使用ob_start(),时间间隔比较大,等待4秒左右就把tcp buffer中的数据发送出去了。数据没有在php buffer中逗留过长时间,就将输出数据发送给了客户端浏览器。这是因为,很快php buffer就被写满了,不得不把数据发送出去。而启用了ob_start(),则不同,发送数据包给客户端,几乎是同一时间发出去的。这就可以推断,数据一直在php buffer中逗留,直到调用了ob_end_flush()才把php buffer中的数据发送给客户端浏览器。

output buffering函数

1.ob_start

激活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中的数据压缩后再传送给浏览器。

2.ob_get_contents

获取一份php buffer中的数据拷贝。值得注意的是,你应该在ob_end_clean()函数调用这调用该函数,否则ob_get_contents()返回一个空字符中。

3.ob_end_flush与ob_end_clean

这二个函数有点相似,都会关闭ouptu_buffering机制。但不同的是,ob_end_flush只是把php buffer中的数据冲(flush/send)到客户端浏览器,而ob_clean_clean将php bufeer中的数据清空(erase),但不发送给客户端浏览器。ob_end_flush调用之后,php buffer中的数据依然存在,ob_get_contents()依然可以获取php buffer中的数据拷贝。而ob_end_clean()调用之后ob_get_contents()取到的是空字符串,同时浏览器也接收不到输出,即没有任何输出。

惯用案例

常常在一些模板引擎和页面文件缓存中看到ob_start()使用。在知名开源项目wordpress,drupal,smarty等地方,都能够发现他们的踪影子。这里抽出drupal的应用。
模板文件

//@file:user-profile.tpl.php
<div>
     <ul>
          <li>username: <?php echo $user->name; ?></li>
          <li>picture:<?php echo $user->picture; ?></li>
     </ul>
</div>//@file:template-render.php
<?php
function theme_render_template($template_file, $variables) {
  if (!<a href="http://www.php.net/is_file">is_file</a>($template_file) { return ""; }
  <a href="http://www.php.net/extract">extract</a>($variables, EXTR_SKIP);
  <a href="http://www.php.net/ob_start">ob_start</a>();
  $contents = <a href="http://www.php.net/ob_get_contents">ob_get_contents</a>();
  <a href="http://www.php.net/ob_end_clean">ob_end_clean</a>();
  return $contents;
}
?>
//@file:profile.php
<?php
$variables = <a href="http://www.php.net/array">array</a>('user' => $user);
print theme_render_template('user-profile.tpl.php', $variables);
?>
PHP 相关文章推荐
PHP 导出数据到淘宝助手CSV的方法分享
Feb 27 PHP
.htaccess文件保护实例讲解
Feb 06 PHP
PHP求小于1000的所有水仙花数的代码
Jan 10 PHP
基于php实现长连接的方法与注意事项的问题
May 10 PHP
PHP代码保护--Zend Guard的使用详解
Jun 03 PHP
新手菜鸟必读:session与cookie的区别
Aug 22 PHP
php中http与https跨域共享session的解决方法
Dec 20 PHP
PHP中imagick函数的中文解释
Jan 21 PHP
php脚本运行时的超时机制详解
Feb 17 PHP
linux下为php添加iconv模块的方法
Feb 28 PHP
PHP检测数据类型的几种方法(总结)
Mar 04 PHP
Yii使用EasyWechat实现小程序获取用户的openID的方法
Apr 29 PHP
PHP内核探索:变量概述
Jan 30 #PHP
PHP内核探索:变量存储与类型使用说明
Jan 30 #PHP
PHP $_FILES中error返回值详解
Jan 30 #PHP
带密匙的php加密解密示例分享
Jan 29 #PHP
PHP过滤★等特殊符号的正则
Jan 27 #PHP
php中自定义函数dump查看数组信息类似var_dump
Jan 27 #PHP
PHP中的按位与和按位或操作示例
Jan 27 #PHP
You might like
Apache+php+mysql在windows下的安装与配置图解(最新版)
2008/11/30 PHP
php根据操作系统转换文件名大小写的方法
2014/02/24 PHP
php实现博客,论坛图片防盗链的方法
2016/10/15 PHP
PHP 搜索查询功能实现
2016/11/29 PHP
tp5 实现列表数据根据状态排序
2019/10/18 PHP
为javascript添加String.Format方法
2020/08/11 Javascript
JQuery读取XML文件数据并显示的实现代码
2009/12/16 Javascript
利用腾讯的ip地址库做ip物理地址定位
2010/07/24 Javascript
jQuery setTimeout传递字符串参数报错的解决方法
2014/06/09 Javascript
学习JavaScript正则表达式
2015/11/13 Javascript
浅谈Cookie的生命周期问题
2016/08/02 Javascript
微信小程序 网络API发起请求详解
2016/11/09 Javascript
js Canvas实现的日历时钟案例分享
2016/12/25 Javascript
JS获取日期的方法实例【昨天,今天,明天,前n天,后n天的日期】
2017/09/28 Javascript
ES6小技巧之代替lodash
2019/06/07 Javascript
Node.js 实现抢票小工具 &amp; 短信通知提醒功能
2019/10/22 Javascript
微信小程序开发之转发分享功能
2019/10/22 Javascript
nest.js 使用express需要提供多个静态目录的操作方法
2019/10/24 Javascript
jquery实现购物车基本功能
2019/10/25 jQuery
JS中this的4种绑定规则详解
2020/02/04 Javascript
javascript 模块依赖管理的本质深入详解
2020/04/30 Javascript
通过js随机函数Math.random实现乱序
2020/05/19 Javascript
[46:44]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD BO3 第二场 3月7日
2021/03/11 DOTA
Python利用Nagios增加微信报警通知的功能
2016/02/18 Python
python编程实现12306的一个小爬虫实例
2017/12/27 Python
python+opencv轮廓检测代码解析
2018/01/05 Python
Python实现读取Properties配置文件的方法
2018/03/29 Python
用TensorFlow实现戴明回归算法的示例
2018/05/02 Python
利用4行Python代码监测每一行程序的运行时间和空间消耗
2020/04/22 Python
HTML5 Canvas draw方法制作动画效果示例
2013/07/11 HTML / CSS
阿提哈德航空官方网站:Etihad Airways
2017/01/06 全球购物
Tirendo比利时:在线购买轮胎
2018/10/22 全球购物
外贸实习生自荐信范文
2013/11/24 职场文书
大学生优秀班干部事迹材料
2014/05/26 职场文书
社区公民道德宣传日活动总结
2015/03/23 职场文书
2019新员工试用期转正工作总结范文
2019/08/21 职场文书