1. 程式人生 > >nginx伺服器ob_flush和flush不起作用

nginx伺服器ob_flush和flush不起作用

做一個逐行輸出,使用ob_flush時試了N種方法不起作用,比如下面的程式碼:

<?php
ob_start();
for(;;)
{
    echo "<br>.......";
    ob_flush();
    flush();
    sleep(1);
}
?>

谷歌了不少的寫法都不行,所以問題應該出在了環境配置上而不是使用方法上。
話說還是stackoverflow給力,搜尋“php flush not working”找到了一個正確的解決方法:
檢查nginx配置檔案(nginx.conf),禁用nginx的buffering:

proxy_buffering off;
gzip off;
fastcgi_keep_conn on;

要注意最後這句fastcgi的哦~~
檢查php.ini,禁用buffering:

output_buffering = off

注意這句配置不能通過ini_set()函式動態在程式中設定,這在php官方手冊中有說明:
the output_buffering setting is PHP_INI_PERDIR therefore it may not be set using ini_set()
經過上面兩步的配置(nginx.conf和php.ini)後,重啟nginx就可以了,再次測試文章開頭的程式碼,成功逐行輸出

補充:PHP flush()與ob_flush()的區別

buffer ---- flush()
 
buffer是一個記憶體地址空間,Linux系統預設大小一般為4096(1kb),即一個記憶體頁。主要用於儲存速度不同步的裝置或者優先順序不同的 裝置之間傳辦理資料的區域。通過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 --- ob_flush()

預設情況下,php buffer是開啟的,而且該buffer預設值是4096,即1kb。你可以通過在php.ini配置檔案中找到output_buffering配置.當echo,print等輸出使用者資料的時候,輸出資料都會寫入到php output_buffering中,直到output_buffering寫滿,會將這些資料通過tcp傳送給瀏覽器顯示。你也可以通過 ob_start()手動啟用php output_buffering機制,使得即便輸出超過了1kb資料,也不真的把資料交給tcp傳給瀏覽器,因為ob_start()將php buffer空間設定到了足夠大 。只有直到指令碼結束,或者呼叫ob_end_flush函式,才會把資料傳送給客戶端瀏覽器。


這兩個函式的使用怕是很多人最迷惑的一個問題,手冊上對兩個函式的解釋也語焉不詳,沒有明確的指出它們的區別,似乎二者的功能都是重新整理輸出快取。但在我們文章一開始的程式碼中如果講fush()替換成ob_flush(),程式就再不能正確執行了。顯然,它們是有區別的,否則也手冊中直接說明其中一個是另外一個函式的別名即可了,沒必要分別說明。那麼它們的區別到底是什麼呢?

在沒有開啟快取時,指令碼輸出的內容都在伺服器端處於等待輸出的狀態 ,flush()可以將等待輸出的內容立即傳送到客戶端。

開啟快取後,指令碼輸出的內容存入了輸出快取中 ,這時沒有處於等待輸出狀態的內容,你直接使用flush()不會向客戶端發出任何內容。而 ob_flush()的作用就是將本來存在輸出快取中的內容取出來,設定為等待輸出狀態,但不會直接傳送到客戶端 ,這時你就需要先使用 ob_flush()再使用flush(),客戶端才能立即獲得指令碼的輸出。

一. flush和ob_flush的正確順序,正確應是,先ob_flush再flush,如下: 
ob_flush();
flush();
如果Web伺服器的作業系統是windows系統,那順序顛倒或者不使用ob_flush()也不會出現問題。[有待求證 ] 但是在Linux系統上就無法重新整理輸出緩衝。

output buffering函式
1.bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
啟用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中的資料壓縮後再傳送給瀏覽器。
第三個引數:是否擦除快取,可選,預設是true,如果設定為false,則在指令碼執行結束前,快取都不會被清除。
2.ob_get_contents
獲取一份php buffer中的資料拷貝。值得注意的是,你應該在ob_end_clean()函式呼叫前呼叫該函式,否則ob_get_contents()返回一個空字元中。

可以使用ob_get_contents()以字串形式獲取服務端快取的資料,
使用ob_end_flush()則會輸出被快取起來的資料,並關閉快取。
而使用ob_end_clean()則會靜默的清除服務端快取的資料,而不會有任何資料或其他行為。
服務端的快取是堆疊起來的,也就是說你在開啟了ob_start()後,關閉之前,在其內部還 可以開啟另外一個快取ob_start()。

不過你也要務必保證關閉快取的操作和開啟快取的運算元量一樣多。 
ob_start() 可以指定一個回撥函式來處理快取資料,如果一個ob_start()內部嵌套了另一個ob_start(),我們假定,外層的ob_start(),編號是A,內層的ob_start()編號是B,它們各自制定了一個回撥函式分別是functionA和functionB,那麼在快取B中的資料輸出時,它會先輩funcitonB回撥函式處理,再交給外層的functionA回撥函式處理,之後才能輸出到客戶端。

另外,手冊說,對於某些web伺服器,比如apache,在使用回撥函式有可能會改變程式當前的工作目錄,解決方法是在回撥函式中自行手動把工作目錄修改回來,用chdir函式,這點似乎不常遇到,遇到的時候記得去查手冊吧。

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_flush()呼叫之後 ob_get_contents()取到的是空字串,同時瀏覽器也接收不到輸出,即沒有任何輸出。

可以使用ob_get_contents()以字串形式獲取服務端快取的資料,使用ob_end_flush()則會輸出被快取起來的資料,並關閉快取。
而使用ob_end_clean()則會靜默的清除服務端快取的資料,而不會有任何資料或其他行為。
服務端的快取是堆疊起來的,也就是說你在開啟了ob_start()後,關閉之前,在其內部還可以開啟另外一個快取ob_start()。不過你也要務必保證關閉快取的操作和開啟快取的運算元量一樣多。
ob_start() 可以指定一個回撥函式來處理快取資料,如果一個ob_start()內部嵌套了另一個ob_start(),我們假定,外層的ob_start(),編號是A,內層的ob_start()編號是B,它們各自制定了一個回撥函式分別是functionA和functionB,那麼在快取B中的資料輸出時,它會先輩funcitonB回撥函式處理,再交給外層的functionA回撥函式處理,之後才能輸出到客戶端。