傳統的B/S結構的應用程式,都是採用"用戶端拉"結束來實現用戶端和伺服器端的資料交換。 本文將通過結合Ticks(可以參看我的另外一篇文章:關於PHP你可能不知道的-PHP的事件驅動化設計 ),來實現一個伺服器推的PHP聊天室簡單構想。 PHPer,尤其是用過set_cookie, header的,一定見過這樣的提示資訊:"Warning: Cannot modify header information - headers already sent by…..", 這是因為通過HTTP協議通訊,資料包會包含倆個部分,一個是Header,一個是data。一般來說,都是先Header部分,在Heaer部分指明了 Data部分的長度,然後使用/r/n/r/n來表示header部分結束,接下來是Data部分。 當我們有任何輸出的時候,Header部分就發送了,這個時候,你再想header函數來改變一些Header部分的域資訊,就會得到上面的提示資訊。 一個簡單的辦法就是使用output_buffering。讓它來快取服務器的輸出,不要太早將Header部分發給用戶端。 那麼,如果不使用output_buffering,是不是就可以實現,每當伺服器有輸出,就立即發送給用戶端呢? 做個如下實驗: //設定php.ini中output_buffering=0 或者使用ob_end_flush()關閉緩衝
set_time_limit(0); for($i=0;$i<10;$i++){ echo "Now Index is :". $i; sleep(1); } 結果我們發現,還是要等到指令碼全部執行完以後,才能一次看到所有的結果。。 為什麼呢? 這是因為我們只是解決了緩衝問題,但是還有一個緩衝問題,PHP會緩衝程式的輸出。所以,這個時候,我們還需要調用,flush(), 來強制使得PHP將所有的程式輸出發送給用戶端。 //設定php.ini中output_buffering=0 ob_end_flush();//關閉緩衝
set_time_limit(0); for($i=0;$i<10;$i++){ echo "Now Index is :". $i; flush(); sleep(1); } 現在是不是看到了,不斷有伺服器的資料顯示出來? 有幾個概念之間的關係,我這裡補充以下: 在代碼中使用ob_start(), 就相當於在php.ini中使用output_buffering=on一樣,使用伺服器緩衝。 在代碼中使用ob_end_flush() 就相當於在php.ini中使用output_buffering = false一樣,關閉伺服器緩衝。 基於前面的討論,我們就有可能使用Ticks來實現,一個無重新整理,無ajax的聊天室: 頁面中包含倆個iframe,一個是不斷擷取聊天室的聊天內容,一個包含使用者發表聊天內容的form. 這樣,在第一個frame的指令碼中: ob_end_clear();//關閉緩衝 set_time_limit(0); ob_implicit_flush(); //這個語句將強制每當有輸出就自動重新整理,相當於在每個echo後,調用flush() $new_mesg = NULL; register_tick_function("getNewMesg"); declare(ticks=1){ while(1){ if(!is_null($new_mesg)){ foreach($new_mesg as $msg){ echo $msg; } $new_mesg = null; } } }
function getNewMesg(){ //通過查詢資料庫,或者共用記憶體,來擷取現在的聊天室大廳的內容。 //返回一個數組,包含所有的新的聊天內容 }
這樣就實現了一個簡單的使用伺服器推技術的聊天室的架構。 當然,關於即時輸出,還有一些其他的限制,比如在PHP5手冊中講到的: 個別web伺服器程式,特別是Win32下的web伺服器程式,在發送結果到瀏覽器之前,仍然會緩衝指令碼的輸出,直到程式結束為止。
有些Apache的模組,比如mod_gzip,可能自己進行輸出緩衝,這將導致flush()函數產生的結果不會立即被發送到用戶端瀏覽器。
甚至瀏覽器也會在顯示之前,緩衝接收到的內容。例如 Netscape 瀏覽器會在接受到換行或 html 標記的開頭之前緩衝內容,並且在接受到 </table> 標記之前,不會顯示出整個表格。
一些版本的 Microsoft Internet Explorer 只有當接受到的256個位元組以後才開始顯示該頁面,所以必鬚髮送一些額外的空格來讓這些瀏覽器顯示頁面內容。
接下來,我貼一個很有趣的代碼,有興趣的同學,可以試試: header(‘Content-type: multipart/x-mixed-replace;boundary=endofsection‘); print " –endofsection "; $pmt = array("-", "/", "|", "/" ); for( $i = 0; $i <10;$i ++ ) { sleep(1); print "Content-type: text/plain "; print "Part $i ".$pmt[$i % 4]; print "–endofsection "; ob_flush(); //強制將緩衝區的內容輸出 flush(); //強制將緩衝區的內容發送給用戶端 } print "Content-type: text/plain "; print "The end "; print "–endofsection– ";
使用firefox開啟,看看你看到了什麼。 這個例子,使用了ob_flush(), 這樣可以在代碼中控制緩衝區內容的輸出時機,更加靈活一些。 |