本篇文章給大家帶來的內容是關於php輸出緩衝區的詳細介紹(代碼示例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。
PHP輸出緩衝區:
緩衝區:實際上是一個內存地址空間。它用來存儲速度不同步的設備或者優先級不同的設備之間傳輸數據的區域。通過緩衝可以使進程之間的交互時間等待變小,從而使從速度慢的設備讀取數據時,速度快的設備的操作進程不發生間斷
PHP的輸出流包含很多內容,通常都是開發者要PHP輸出的文本,這些文本大多是用echo或printf()函數來輸出的
1>任何輸出內容的函數都會用到輸出緩衝區
這是指正常的PHP腳本,如果開發的是PHP擴展,使用的函數(C函數)可能會直接輸出寫到SAPI緩衝區層,不需要經過輸出緩衝層(我們可以在PHP源文件 main/php_output.h了解這些C函數的API文檔)
2>輸出緩衝區不是唯一用於緩衝輸出的層,它實際上只是很多層中的一個,輸出緩衝層的行為與使用的SAPI(Web或CLI)有關,不同的SAPI可能有不同的行為
緩衝邏輯關係圖
最上端的兩次就是我們通常所認識的『輸出緩衝區』
3>SAPI中的輸出緩衝區,這些都是PHP中的層,當輸出的位元組離開PHP進入計算機體系結構中的更底層時,緩衝區又會不斷出現(終端緩衝區(terminal buffer)、fast-cgi緩衝區、Web伺服器緩衝區、作業系統緩衝區、TCP/IP棧緩衝區等)。
PHP CLI的SAPI有點特殊,CLI也稱命令行介面,它會將php.ini配置中output_buffer選項強制設置為0,這表示禁用默認PHP輸出緩衝區,所以在CLI中,默認情況下你要輸出的內容會直接傳遞到SAPI層,除非你手動調用ob_()類函數,並且在CLI中,implicit_flush的值也會被設置為1,
註:我們經常混淆implicit_flush的作用,php的原始碼已說明一切:當implicit_flush被設置為打開(值為1)時,一旦有任何輸出寫到SAPI緩衝區,它都會立刻刷新(flush,意思把這些數據寫到更底層,並且緩衝區會被清空)
也就是說任何數據到CLI SAPI中時,CLI SAPI都會立即將這些數據仍到它的下一層去,一般會是標準輸出管到,write()和fflush()這兩個函數就是負責做這件事情的
默認PHP輸出緩衝區:如果使用不同於CLI的SAPI,比如PHP-FPM,會用到下面3個與緩衝區相關的php.ini配置選項
output_buffering
implicit_flush
output_handler
首先不能再運行時使用ini_set()函數修改這幾個選項的值,因為這些值會在PHP程序啟動的時候,還沒有運行任何腳本之前解析,所以在運行時可以使用ini_set()改變值,但是並不會生效。我們只能通過編輯php.ini文件或者是在執行PHP程序的時候使用-d選項才能改變它們的值
默認情況下,PHP發行版會在php.ini中會把output_buffering設置為4096個位元組,如果將它的值設置為ON,那麼默認的輸出緩衝區的大小為16KB
在Web應用環境中對輸出的內容使用緩衝區對性能有好處:
默認的4K的設置是一個合適的值,意味著你可以先寫入4096個ASCLL字符,然後在與下面的SAPI層通信,並且在Web應用環境中,通過Socket一個位元組一個位元組地傳輸消息的方式對性能並不好,更好的方式是把所有內容一次性傳輸給伺服器,或者至少是一塊一塊地傳輸,層與層之間的數據交換次數越少,性能越好,應該總是保持輸出緩衝區處於可用狀態,PHP會負責在請求結束後把它們中的內容傳輸給終端用戶,開發者不用做任何事
implicit_flush默認是設置為關閉,這樣的話新數據寫入就不會刷新SAPI,對於FastCGI協議,刷新操作是每次寫入後都發送一個FastCGI數組包,如果發送數據包之前先把FastCGI的緩衝器寫滿會更好。如果需要手動刷新SAPI的緩衝區,使用flush()函數,如果想寫一個就刷新一次可以設置implicit_flush或者調用一次ob_implicit_flush()函數
推薦使用配置:
output_buffering=4096
implicit_flush = Off/no
要修改輸出緩衝區的大小,硬確保使用的值是4/8的倍數,它們分別是32/64位作業系統
output_handler是一個回調函數,它可以在緩衝區刷新之前修改緩衝區中的內容
緩衝區中的內容會傳遞給你選擇的回調函數(只能用一個)來執行內容轉換的工作,所以說如果想獲取PHP給Web伺服器以及用戶的內容,可以使用輸出緩衝區回調,輸出是指:消息頭、消息頭。HTTP的消息頭也是輸出緩衝區層的一部分
消息頭和消息體:
實際上,任何與消息頭的輸出有關的PHP函數(header()、setcookie()、session_start())都使用內部的sapi_header_op函數,這個函數只會把內容寫入消息頭緩衝區中。 當我們如使用printf()函數的時候,內容會先被寫入到輸出緩衝區(可能是多個),當這個輸出環城區的內容需要被發送的時候,PHP會先發送消息頭,然後發送消息體。PHP為了搞定了所有的事情,如果想自己動手,那就只能禁掉輸出緩衝區
用戶輸出緩衝區:
如果想使用默認PHP輸出緩衝區層,就不能使用CLI,因為它已經禁用了這個層
/*launched via php -d output_buffering=32 -d implicit_flush=1
* */
echo str_repeat('a',31);
sleep(3);
echo 'b';
sleep(3);
echo 'c';
?>
默認輸出緩衝區的大小設置32位元組,程序運行會先寫入31位元組,然後休眠,然後在寫入1一個位元組,這個位元組填滿緩衝區,它會立即刷新自身,把裡面的數據傳遞給SAPI層的緩衝區,因為把implicit_flush設置為1,所以SAPI層的緩衝區也會立即刷新到下一層,所以輸出aa...b,然後休眠,然後在輸出一個位元組,此時緩衝區有31空位元組,但是腳本執行完畢,所以包含這個位元組的緩衝區也會立即刷新,從而會在螢幕輸出c
用戶輸出緩衝區:通過ob_start()創建,我們可以創建多個這種緩衝區(直到內存耗盡為止),這些緩衝區組成一個堆棧結構,每個新建緩衝區都會堆疊到之前的緩衝區上,每次當它被填滿或者溢出,都會執行刷新操作,然後把其中的數據傳遞給下一個緩衝區
function callback($buffer)
{
// replace all the apples with oranges
return ucfirst($buffer);
}
function callback1($buffer)
{
// replace all the apples with oranges
static $a=0;
return $a++.'-'.$buffer."\\n";
}
ob_start('callback1',10);
ob_start("callback",3);
echo "fo";
sleep(2);
echo 'o';
sleep(2);
echo "barbazz";
sleep(2);
echo "hello";
按照棧的先進後出原則,任何輸出都會先存放在緩衝區2中。緩衝區2的大小為3個位元組,所以當第一個echo語句輸出的字符串'fo'會先存放在緩衝區2中,還差一個字符,當第二個echo語句輸出'o'後,緩衝區2滿了,所以它會刷新(flush)。在刷新之前先調用ob_start()的回調函數,這個函數將緩衝區的首字母轉換成大寫,所以輸出為『Foo』,然後它會被保存在緩衝區1中,第三個輸出為『barbazz』,它還是會先放在緩衝區2中,這個字符串有7個位元組,緩衝區2已經溢出了,所以它立刻刷新,調用回調函數得到的結構是『Barbazz』,然後傳遞給緩衝區1中,這個緩衝區1中保存了'FooBarbazz'這十個字符,緩衝區1會刷新,同樣的先會調用ob_start()的回調函數,緩衝區1的回調函數會在字符串簽名添加型號,所以第一行是'0-FooBarbazz',
最後一個echo語句輸出一個字符串'hello',它大於三個字符,所以會觸發緩衝區2,因為此時腳本執行完畢,所以也會立即刷新緩衝區1,得到 『1-Hello』。
因此使用echo函數如此簡單的事情,如果涉及緩衝區和性能也是複雜的,所以要注意使用echo輸出內容的大小,如果緩衝區配置與輸出內容相似,那麼性能會比較優良,如果緩衝器配置小於輸出內容,需要在應用中輸出的內容做切分處理。
輸出緩衝區的機制:主要是在5.4以後整個緩衝層都被重寫,我們自己開發PECL擴展時,可以聲明屬於自己的輸出緩衝區回調方法,這樣可以於其他PECL擴展做區分,避免產生衝突
輸出緩衝區的陷阱:
有些PHP的內部函數也使用了輸出緩衝區,它們會疊加到其他的緩衝區上,這些函數會填滿自己的緩衝區然後刷新,或者返回裡面的內容,比如print_r()、higglight_file()和highlight_file::handle()都是此類,所以不應該在輸出緩衝區的回調函數中使用這些函數,這樣會導致未定義的錯誤
同樣的道理,當PHP執行echo、print時,也不會立即通過tcp輸出到瀏覽器,而時將數據先寫入PHP的默認緩衝區,我們可以理解PHP有一套自己的輸出緩衝機制,在傳送給系統緩存之前建立一個新的隊列,數據經過該隊列,當一個PHP緩衝區寫滿以及腳本執行邏輯需要輸出時,腳本會把裡面的數據傳輸給SAPI瀏覽器
echo/print->php輸出緩衝區->SAPI緩衝區->TCP緩衝區->瀏覽器緩衝區->瀏覽器展示
ob_flush()和flush()區別:
ob_flush():把數據從php的緩衝區中釋放出來
flush():把不再緩衝區中的或者說是被釋放出來的數據發送到瀏覽器,嚴格來講, 這個只有在PHP做為apache的Module(handler或者filter)安裝的時候, 才有實際作用. 它是刷新WebServer(可以認為特指apache)的緩衝區.
在nginx中ob_flush和flush兩個都失效:
解決辦法: 發現在nginx的配置中,有如下的設置
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k;
Nginx會緩衝PHP輸出的信息,當達到128k時才會將緩衝區的數據發送給客戶端,那麼我們首先需要將這個緩衝區調小
比如:
fastcgi_buffer_size 4k;
fastcgi_buffers 8 4k;
並且,必須禁用gzip
gzip off;
然後,在php中,在ob_flush和flush前,輸出一段達到4k的內容,例如:
echo str_repeat(『 『, 1024*4);
到此,PHP就可以正常通過ob_flush和flush逐行輸出需要的內容了。
輸出緩衝區實踐
1> 通過ob_start()函數手動處理PHP緩衝區機制,這樣即便輸出內容超過配置參數大小,也不會把數據傳輸給瀏覽器,ob_start()將PHP緩衝區空間設置到足夠大,只有腳本執行結束後或調用ob_end_flush()函數,才會把數據發送給瀏覽器
for($i=0;$i<10;$i++){
echo $i.'
';
sleep($i+1);
}
執行之後不會每隔幾秒就有輸出,知道腳本循環結束後,才會一次性輸出,這是因為數據量太小,輸出緩衝區沒有寫滿
2>當修改output_buffering=0
for($i=0;$i<10;$i++){
echo $i.'
';
flush();
sleep($i+1);
}
因為緩衝區的容量設置為0,禁用PHP緩衝區機制,這是我們在瀏覽器看到斷斷續續輸出,而不必等到腳本執行完畢才看到輸出,這是因為數據沒有在緩存中停留
3>我們把參數修改為output_buffering=4096,輸出數據大於一個緩衝區,不調用ob_start()函數
首先先輸出一個4k的內容記下來加本來輸出的內容:
for($i=0;$i<10;$i++){
echo echo str_repeat(' ', 1024*4*8).$i
;
sleep($i);
}
發現可以HTTP連接未關閉,可以看到間斷輸出,儘管啟用了PHP輸出緩衝區機制,但是也不是一次性輸出,這還是因為PHP緩衝區空間不夠,每寫滿一個緩衝區,數據就會發送到瀏覽器。
4>參照上例子,這次我們調用ob_start()
ob_start(); //開啟PHP緩衝區
for($i=0;$i<10;$i++){
echo echo str_repeat(' ', 1024*4*8).$i
;
sleep($i);
}
等到服務端腳本全部處理完,響應結束才會看到完整的輸出,在輸出前瀏覽器會一直保持空白,這是因為,PHP一旦調用了ob_start()會將PHP緩衝區擴展到足夠大,知道ob_end_flush函數調用或者腳本運行結束才發送PHP緩衝區中的數據到客戶端瀏覽器
可以通過tcpdump命令監控TCP的報文,來觀察一下使用ob_start()和沒有使用它的區別
總結:ob_start激活output_buffering機制,一旦激活,腳本不再直接輸出給瀏覽器,而是先暫時寫入PHP緩衝區
PHP默認開啟out_buffering機制,通過調用ob_start函數把output_buffering值擴展到足夠大,也可以通過$chunk_size來指定output_buffering的值,$chunk_size默認值是0,表示直到腳本運行結束後,PHP緩衝區中的數據才會發送到瀏覽器,若設置了$chunk_size的大小,則只要緩衝區達到這個值,就會發送給瀏覽器你
可以通過指定output_callback參數來處理PHP緩衝區的數據,比如ob_gzhandler()將緩衝區中的數據壓縮後傳送給瀏覽器,ob_get_contents()是獲取一份PHP緩衝區中的數據拷貝
ob_start();
echo date('Y-m-d h:i:s');
$output=ob_get_contents();
ob_end_flush();
echo ''.$output;
後者是從ob_get_contents取的緩衝區的內容
ob_end_flush()與ob_end_clean(0這兩個函數都會關閉輸出緩衝,區別是前者只是把PHP緩衝中的數據發生給客戶端瀏覽器,而後者將PHP緩衝區中的數據刪掉,但不發送給客戶端,前者調用之後數據依然存在,ob_get_contents()依然可以獲取PHP緩衝區中的數據拷貝
輸出緩衝與靜態頁面:
大家都知道靜態頁面的加載速度快,不用請求資料庫,以下就是生成靜態頁面的腳本代碼
echo str_pad('',1024);//使緩衝區溢出
ob_start();//打開緩衝區
$content=ob_get_contents();//獲取緩衝區內容
$f=fopen('./index.html','w');
fwrite($f,$content);//寫入到文件
fclose($f);
ob_end_clean()清空並關閉緩衝區
在一些模板引擎和頁面文件緩衝中ob_start()函數被使用,如 WP、Drupal、Smarty,例如:
在Wp經常在主題目錄header.php看到類似的代碼:
只有一個flush(),它的所在的位置就是告訴瀏覽器那一部分的緩存需要更新,即頁面頭部以上部分需緩存
以及Wp的部分代碼,可以看到,在緩衝區開啟時,加入自己的回調方法
內容壓縮輸出:就是把輸出到客戶端瀏覽器的內容進行壓縮
好處:降低客戶端對伺服器出口帶寬的占用,提升帶寬的利用率。降低Web伺服器(如Nginx、Apache、Tomcat等)處理文本時引入的開銷,用戶端可以減少網絡傳輸延時對用戶體驗的影響,降低瀏覽器加載頁面內容時占用的內存,有利於改善瀏覽器穩定性
ob_start('ob_gzhandler');//使用gz格式壓縮輸出
print'my contents';
ob_end_flush();
之壓縮當前腳本與緩衝區,對其他腳本沒有影響,PHP還提供另外一種壓縮方式,在php.ini修改
zlib_output_compression=On
這樣輸出時所有頁面都以zlib的壓縮方式輸出,但是兩者混用是毫無意義的,只會額外消耗CPU性能,讓它壓縮已經壓縮好的內容,但是基於實踐,使用PHP壓縮效果並不是十分理想,通常做法是放在Web伺服器,比如Apache啟用defate、Nginx使用gzip的方式都比PHP端壓縮效果好得多
輸出緩衝允許第三方庫和應用框架(Laravel、TP等)開發者完全控制它們自己輸出的內容。比如把他們放在一個全局緩衝區中處理,對於任何輸出流的內容(如數據壓縮)和任何HTTP消息頭、PHP都以正確的書序發送,使用輸出緩衝區能夠有效地節省帶寬,比如圖片、字體、CSS、JS等前端內容,特別是限制前端框架也越來越來,讓它使用戶反應速度更快,從而有效提高系統性能
以上就是php輸出緩衝區的詳細介紹(代碼示例)的詳細內容,更多請關注其它相關文章!
更多技巧請《轉發 + 關注》哦!