首頁  >  文章  >  後端開發  >  php輸出緩衝區的詳細介紹(程式碼範例)

php輸出緩衝區的詳細介紹(程式碼範例)

不言
不言轉載
2019-03-01 11:59:452682瀏覽

這篇文章帶給大家的內容是關於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()函數
      建議使用設定:
      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_flushflush前,输出一段达到4k的内容,例如:

            echo str_repeat(‘ ‘, 1024*4);

到此,PHP就可以正常通过ob_flushflush逐行输出需要的内容了。

输出缓冲区实践

    1> 通过ob_start()函数手动处理PHP缓冲区机制,这样即便输出内容超过配置参数大小,也不会把数据传输给浏览器,ob_start()将PHP缓冲区空间设置到足够大,只有脚本执行结束后或调用ob_end_flush()函数,才会把数据发送给浏览器 

for($i=0;$i<10;$i++){
echo $i.&#39;<br/>&#39;;
sleep($i+1);
}

 执行之后不会每隔几秒就有输出,知道脚本循环结束后,才会一次性输出,这是因为数据量太小,输出缓冲区没有写满

2>当修改output_buffering=0

for($i=0;$i<10;$i++){
echo $i.&#39;<br/>&#39;;
flush();
sleep($i+1);
}

 因为缓冲区的容量设置为0,禁用PHP缓冲区机制,这是我们在浏览器看到断断续续输出,而不必等到脚本执行完毕才看到输出,这是因为数据没有在缓存中停留

3>我们把参数修改为output_buffering=4096,输出数据大于一个缓冲区,不调用ob_start()函数

    首先先输出一个4k的内容记下来加本来输出的内容:   

for($i=0;$i<10;$i++){
echo   echo str_repeat(&#39; &#39;, 1024*4*8).$i<br/>;
sleep($i);
}

发现可以HTTP连接未关闭,可以看到间断输出,尽管启用了PHP输出缓冲区机制,但是也不是一次性输出,这还是因为PHP缓冲区空间不够,每写满一个缓冲区,数据就会发送到浏览器。

4>参照上例子,这次我们调用ob_start()

ob_start(); //开启PHP缓冲区
for($i=0;$i<10;$i++){
echo   echo str_repeat(&#39; &#39;, 1024*4*8).$i<br/>;
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(&#39;Y-m-d h:i:s&#39;);

$output=ob_get_contents();
ob_end_flush();
echo &#39;<!output>&#39;.$output;

 后者是从ob_get_contents取的缓冲区的内容

 ob_end_flush()与ob_end_clean(0这两个函数都会关闭输出缓冲,区别是前者只是把PHP缓冲中的数据发生给客户端浏览器,而后者将PHP缓冲区中的数据删掉,但不发送给客户端,前者调用之后数据依然存在,ob_get_contents()依然可以获取PHP缓冲区中的数据拷贝

输出缓冲与静态页面

     大家都知道静态页面的加载速度快,不用请求数据库,以下就是生成静态页面的脚本代码

echo str_pad(&#39;&#39;,1024);//使缓冲区溢出
ob_start();//打开缓冲区
$content=ob_get_contents();//获取缓冲区内容
$f=fopen(&#39;./index.html&#39;,&#39;w&#39;);
fwrite($f,$content);//写入到文件
fclose($f);
ob_end_clean()清空并关闭缓冲区

  在一些模板引擎和页面文件缓冲中ob_start()函数被使用,如 WP、Drupal、Smarty,例如:

在Wp经常在主题目录header.php看到类似的代码:

只有一个flush(),它的所在的位置就是告诉浏览器那一部分的缓存需要更新,即页面头部以上部分需缓存

以及Wp的部分代码,可以看到,在缓冲区开启时,加入自己的回调方法
 内容压缩输出:就是把输出到客户端浏览器的内容进行压缩

好处:降低客户端对服务器出口带宽的占用,提升带宽的利用率。降低Web服务器(如Nginx、Apache、Tomcat等)处理文本时引入的开销,用户端可以减少网络传输延时对用户体验的影响,降低浏览器加载页面内容时占用的内存,有利于改善浏览器稳定性 

ob_start(&#39;ob_gzhandler&#39;);//使用gz格式压缩输出
print&#39;my contents&#39;;
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輸出緩衝區的詳細介紹(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除