· 作者:laruence(http://www.laruence.com/)
· 本文地址: http://www.laruence.com/2008/04/16/118.html
· 轉載請註明出處
傳統的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會緩衝程序的輸出。所以,這個時候,我們還需要調用,flush(), 來強制使得PHP將所有的程序輸出發送給客戶端。
現在是不是看到了,不斷有服務器的數據顯示出來?
有幾個概念之間的關係,我這裏補充以下:
在代碼中使用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手冊中講到的:
接下來,我貼一個很有趣的代碼,有興趣的同學,可以試試:
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(), 這樣可以在代碼中控制緩存區內容的輸出時機,更加靈活一些。
· 本文地址: http://www.laruence.com/2008/04/16/118.html
· 轉載請註明出處
傳統的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);
}
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_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個字節以後纔開始顯示該頁面,所以必須發送一些額外的空格來讓這些瀏覽器顯示頁面內容。
有些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-- ";
這個例子,使用了ob_flush(), 這樣可以在代碼中控制緩存區內容的輸出時機,更加靈活一些。