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制作静态网站的模板框架(一)
Oct 09 PHP
用PHP查询域名状态whois的类
Nov 25 PHP
php 强制下载文件实现代码
Oct 28 PHP
json的键名为数字时的调用方式(示例代码)
Nov 15 PHP
php中让人头疼的浮点数运算分析
Oct 10 PHP
PHP实现判断数组是一维、二维或几维的方法
Feb 06 PHP
php可变长参数处理函数详解
Feb 22 PHP
PHP中“=&gt;
Mar 01 PHP
PHP工厂模式的日常使用
Mar 20 PHP
php常用字符串长度函数strlen()与mb_strlen()用法实例分析
Jun 25 PHP
基于Laravel(5.4版本)的基本增删改查操作方法
Oct 11 PHP
php模拟实现斗地主发牌
Apr 22 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
php 仿Comsenz安装效果代码打包提供下载
2010/05/09 PHP
表格展示无限级分类(PHP版)
2012/08/21 PHP
解析php中两种缩放图片的函数,为图片添加水印
2013/06/14 PHP
php对文件进行hash运算的方法
2015/04/03 PHP
twig模板常用语句实例小结
2016/02/04 PHP
Yii2框架dropDownList下拉菜单用法实例分析
2016/07/18 PHP
PDO的安全处理与事物处理方法
2016/10/31 PHP
兼容IE和Firefox的javascript获取iframe文档内容的函数
2011/08/15 Javascript
jQuery.event兼容各浏览器的event详细解析
2013/12/18 Javascript
JS在IE下缺少标识符的错误
2014/07/23 Javascript
纯js实现无限空间大小的本地存储
2015/06/18 Javascript
jQuery实现移动端手机商城购物车功能
2016/09/24 Javascript
Js利用console计算代码运行时间的方法示例
2017/09/24 Javascript
使用socket.io制做简易WEB聊天室
2018/01/02 Javascript
使用webpack打包koa2 框架app
2018/02/02 Javascript
微信小程序模拟cookie的实现
2018/06/20 Javascript
vue监听input标签的value值方法
2018/08/27 Javascript
基于Vue SEO的四种方案(小结)
2019/07/01 Javascript
JavaScript实现点击切换功能
2021/01/27 Javascript
[01:08:17]2018DOTA2亚洲邀请赛3月29日 小组赛B组 EG VS VGJ.T
2018/03/30 DOTA
在CentOS上配置Nginx+Gunicorn+Python+Flask环境的教程
2016/06/07 Python
Python设计模式之组合模式原理与用法实例分析
2019/01/11 Python
使用Matplotlib 绘制精美的数学图形例子
2019/12/13 Python
多个python文件调用logging模块报错误
2020/02/12 Python
python-xpath获取html文档的部分内容
2020/03/06 Python
sklearn和keras的数据切分与交叉验证的实例详解
2020/06/19 Python
django使用graphql的实例
2020/09/02 Python
Stefania Mode美国:奢华设计师和时尚服装
2018/01/07 全球购物
奥地利婴儿用品和玩具购物网站:baby-markt.at
2020/01/26 全球购物
计算机专业求职信
2014/06/02 职场文书
青春励志演讲稿范文
2014/08/25 职场文书
群众路线剖析材料范文
2014/10/09 职场文书
党员民主生活会材料
2014/12/15 职场文书
交通事故被告代理词
2015/05/23 职场文书
给numpy.array增加维度的超简单方法
2021/06/02 Python
英国数字版游戏销量周榜公布 《小缇娜的奇幻之地》登顶
2022/04/03 其他游戏