程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP基礎知識 >> php緩沖 output

php緩沖 output

編輯:PHP基礎知識
 

php緩沖 output_buffering和ob_start

 

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/>';
    sleep($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/>';
    flush();  //通知操作系統底層,盡快把數據給客戶端浏覽器
    sleep($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 file_get_contents('./f4096') . $i . '<br/>';
    sleep($i +1);
}
?>


現象:響應還沒結束(http連接沒有關閉),斷斷續續可以看到間斷性輸出,浏覽器界面不會一直保持空白。盡管啟用了php output_buffering機制,但依然會間斷性輸出,而不是一次性輸出,是因為output_buffering空間不夠用。每寫滿一個php buffering,數據就會發送到客戶端浏覽器。

4.當output_buffering=4096, 輸出數據大於一個tcp buffer, 調用ob_start()

<?php
ob_start();	//開啟php buffer
for ($i = 0; $i < 10; $i++) {
    echo file_get_contents('./f4096') . $i . '<br/>';
    sleep($i + 1);
}
ob_end_flush();
?>


現象:直到服務端腳本處理完成,響應結束,才看到完整輸,輸出間隔時間很短,以至你感受不到停頓。在輸出之前,浏覽器一直保持著空白界面,等待服務端數據。這是因為,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 (!is_file($template_file) { return ""; }
  extract($variables, EXTR_SKIP);
  ob_start();
  $contents = ob_get_contents();
  ob_end_clean();
  return $contents;
}
?>

 //@file:profile.php
<?php
$variables = array('user' => $user);
print theme_render_template('user-profile.tpl.php', $variables);
?>

----------------------------
 

<?php
ob_start();
setcookie("username","aaa",time()+3600);
echo "the username is:".$HTTP_COOKIE_VARS["username"]."\n";
echo "the username is:".$_COOKIE["username"]."\n";
print_r($_COOKIE);
?>
Warning: Cannot modify header information - headers already sent by出錯的原因
我在php程序的頭部加了,
header("cache-control:no-cache,must-ridate");
之後頁面就出現上面的錯誤,看了N個資料也沒有結果。

今天偶爾發現原來是我的php.ini裡面的配置出了問題,找到php.ini文件
output_buffering默認為off的。我現在把它設為4096就OK了。
用於解決顯示提示錯誤,不能按(日期+導出文件數)為文件名的錯誤信息.
setcookie函數必須在任何資料輸出至浏覽器前,就先送出
基於上面這些限制,所以執行setcookie()函數時,

常會碰到"Undefined index"、

"Cannot modify header information - headers already sent by"…等問題,

解決"Cannot modify header information - headers already sent by"這個錯誤的方法是在產生cookie前,先延緩資料輸出至瀏覽器,

因此,您可以在程式的最前方加上ob_start();這個函數。
ob_start()函數用於打開緩沖區,比如header()函數之前如果就有輸出,包括回車\空格\換行\都會有"Header had all ready send by"的錯誤,這時可以先用ob_start()打開緩沖區PHP代碼的數據塊和echo()輸出都會進入緩沖區而不會立刻輸出.當然打開緩沖區的作用很 多,只要發揮你的想象.可以總結以下四點:

1.用於header()之前

ob_start(); //打開緩沖區
echo \"Hellon\"; //輸出
header("location:index.php"); //把浏覽器重定向到index.php
ob_end_flush();//輸出全部內容到浏覽器
?>

2.phpinfo()函數可獲取客戶端和服務器端的信息,但要保存客戶端信息用緩沖區的方法是最好的選擇.
ob_start(); //打開緩沖區
phpinfo(); //使用phpinfo函數
$info=ob_get_contents(); //得到緩沖區的內容並且賦值給$info
$file=fopen(\'info.txt\',\'w\'); //打開文件info.txt
fwrite($file,$info); //寫入信息到info.txt
fclose($file); //關閉文件info.txt
?>

3.靜態頁面技術
ob_start();//打開緩沖區
?>
php頁面的全部輸出
$content = ob_get_contents();//取得php頁面輸出的全部內容
$fp = fopen("output00001.html", "w"); //創建一個文件,並打開,准備寫入
fwrite($fp, $content); //把php頁面的內容全部寫入output00001.html,然後……
fclose($fp);
?>

4.輸出代碼
Function run_code($code) {
If($code) {
ob_start();
($code);
$contents = ob_get_contents();
ob_end_clean();
}else {
echo "錯誤!沒有輸出";
exit();
}
return $contents;
}
 

 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved