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 相关文章推荐
dedecms防止FCK乱格式化你的代码的修改方法
Mar 17 PHP
php下HTTP Response中的Chunked编码实现方法
Nov 19 PHP
php快速url重写 更新版[需php 5.30以上]
Apr 20 PHP
php入门学习知识点八 PHP中for循环基本应用之九九乘法口绝表
Jul 14 PHP
Ping服务的php实现方法,让网站快速被收录
Feb 04 PHP
PHP date函数常用时间处理方法
May 11 PHP
php实现编辑和保存文件的方法
Jul 20 PHP
如何使用PHP对网站验证码进行破解
Sep 17 PHP
PHP实现二叉树的深度优先与广度优先遍历方法
Sep 28 PHP
PHP等比例压缩图片的实例代码
Jul 26 PHP
PHP 并发场景的几种解决方案
Jun 14 PHP
详解no input file specified 三种解决方法
Nov 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
CI(CodeIgniter)模型用法实例分析
2016/01/20 PHP
几行代码轻松实现PHP文件打包下载zip
2017/03/01 PHP
laravel实现按时间日期进行分组统计方法示例
2019/03/23 PHP
php设计模式之原型模式分析【星际争霸游戏案例】
2020/03/23 PHP
php如何获取Http请求
2020/04/30 PHP
cssQuery()的下载与使用方法
2007/01/12 Javascript
JavaScript随机排序(随即出牌)
2010/09/17 Javascript
js内存泄露的几种情况详细探讨
2013/05/31 Javascript
jQuery验证插件 Validate详解
2014/11/20 Javascript
jquery中change()用法实例分析
2015/02/06 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
2015/12/01 Javascript
Javascript中的迭代、归并方法详解
2016/06/14 Javascript
JS控制层作圆周运动的方法
2016/06/20 Javascript
vue+webpack实现异步组件加载的方法
2018/02/03 Javascript
Vue实现active点击切换方法
2018/03/16 Javascript
Less 安装及基本用法
2018/05/05 Javascript
js变量声明var使用与不使用的区别详解
2019/01/21 Javascript
vue移动端模态框(可传参)的实现
2019/11/20 Javascript
浅谈vue项目利用Hbuilder打包成APP流程,以及遇到的坑
2020/09/12 Javascript
Python中的字典遍历备忘
2015/01/17 Python
python IDLE 背景以及字体大小的修改方法
2019/07/12 Python
iRobot官网:改变生活的家用机器人品牌
2016/09/20 全球购物
澳大利亚制造的羊皮靴:Original UGG Boots
2017/11/13 全球购物
西班牙国家航空官方网站:Iberia
2017/11/16 全球购物
实习销售业务员自我鉴定
2013/09/21 职场文书
小学教育毕业生自荐信
2013/11/18 职场文书
机电工程学生自荐信范文
2013/12/07 职场文书
餐饮加盟计划书
2014/01/10 职场文书
舞蹈教师自荐信
2014/01/27 职场文书
经贸专业毕业生求职信
2014/03/23 职场文书
《苏珊的帽子》教学反思
2014/04/07 职场文书
护理专科毕业自荐信范文
2014/04/21 职场文书
电子信息工程自荐信
2014/05/26 职场文书
财政局党的群众路线教育实践活动剖析材料
2014/10/13 职场文书
详解Django中 render() 函数的使用方法
2021/04/22 Python
动画「进击的巨人」第86话播出感谢绘公开
2022/03/21 日漫