反向推送技術現在非常流行, 而長輪詢是實現反向推送的關鍵技術之一.
//如果要轉載本文請注明出處,免的出現版權紛爭,我不喜歡看到那種轉載了我的作品卻不注明出處的人 Seven{See7di#Gmail.com}
HTTP 協議的成功毋庸置疑。它是 Internet上大部分信息交換的基礎。然而,它也有一些局限性。特別是,它是無狀態、單向的協議。請求被發送到 Web 服務器,服務器處理請求並發回一個響應 —僅此而已。請求必須由客戶機發出,而服務器則只能在對請求的響應中發送數據。這至少會影響很多類型的 Web應用程序的實用性。典型的例子就是聊天程序。另外還有一些例子,例如比賽的比分或電子郵件程序。
HTTP的這些局限性也是它取得一定成功的原因。請求/響應周期使它成為了經典的模型,即每個連接使用一個線程。只要能夠快速為請求提供服務,這種方法就有巨大的可伸縮性。每秒鍾可以處理大量的請求,只需使用少量的服務器就可以處理很大數量的用戶。對於很多經典的 Web應用程序,例如內容管理系統、搜索應用程序和電子商務站 點等等而言,這非常適合。在以上任何一種 Web應用程序中,服務器提供用戶請求的數據,然后關閉連接,並釋放那個線程,使之可以為其他請求服務。如果提供初始數據之后仍可能存在交互,那么將連接保持為打開狀態,因此線程就不能釋放出來,服務器也就不能為很多用戶服務。
但是,如果想在對請求做出響應並發送初始數據之后,仍然保持與用戶的交互呢?在 Web 早期,這一點常使用 meta刷新實現。這將自動指示瀏覽器在指定秒數之后重新裝載頁面,從而支持簡陋的輪詢(polling)。這不僅是一種糟糕的用戶體驗,而且通常效率非常低下。如果沒有新的數據要顯示在頁面上呢?這時不得不重新呈現同樣的頁面。如果對頁面的更改很少,並且頁面的大部分沒有變化呢?同樣,不管是否有必要,都得重新請求和獲取頁面上的一切內容。
Ajax 的發明和流行改變了上述狀況。現在,服務器可以異步通信,因此不必重新請求整個頁面。現在可以進行增量式的更新。只需使用XMLHttpRequest 輪詢服務器。這項技術通常被稱作Comet。這項技術存在一些變體,每種變體具有不同的性能和可伸縮性。我們來看看這些不同風格的 Comet。
Comet 風格
Ajax 的出現使 Comet 成為可能。HTTP的單向性質可以有效地加以規避。實際上有一些不同的方法可以繞過這一點。您可能已經猜到,支持 Comet 的最容易的方式是輪詢(poll)。使用XMLHttpRequest 向服務器發出調用,返回后,等待一段固定的時間(通常使用 JavaScript 的 setTimeout函數),然后再次調用。這是一項非常常見的技術。例如,大多數 webmail 應用程序就是通過這種技術在電子郵件到達時顯示電子郵件的。
這項技術有優點也有缺點。在這種情況下,您期望快速返回響應,就像任何其他 Ajax請求一樣。在請求之間必須有一段暫停。否則,連續不斷的請求會沖垮服務器,並且這種情況下顯然不具有可伸縮性。這段暫停使應用程序產生一個延時。暫停的時間越長,服務器上的新數據就需要越多的時間才能到達客戶機。如果縮短暫停時間,又將重新面臨沖垮服務器的風險。但是另一方面,這顯然是最簡單的實現Comet 的方式。
現在應該指出,很多人認為輪詢並不屬於 Comet。相反,他們認為 Comet 是對輪詢的局限性的一個解決方 案。最常見的 “真正的”Comet 技術是輪詢的一種變體,即長輪詢(longpolling)。輪詢與長輪詢之間的主要區別在於服務器花多長的時間作出響應。長輪詢通常將連接保持一段較長的時間 —通常是數秒鍾,但是也可能是一分鍾甚至更長。當服務器上發生某個事件時,響應被發送並隨即關閉,輪詢立即重新開始。
長輪詢相對於一般輪詢的優點在於,數據一旦可用,便立即從服務器發送到客戶機。請求可能等待較長的時間,期間沒有任何數據返回,但是一旦有了新的數據,它將立即被發送到客戶機。因此沒有延時。如果您使用過基於 Web 的聊天程序,或者聲稱 “實時” 的任何程序,那么它很可能就是使用了這種技術。
下面我就Flex和PHP來舉例說明一下,常輪詢如何實現.
首先將一下PHP端的代碼,很簡單
//timeout in seconds
$timeout = 60;
// log start time
$start_time = time();
// get messge from local file
function get_msg(){
return file_get_contents('msg.txt');
}
// get message
$last_msg = get_msg();
// start the loop
while (true){
// get current time
$current_time = time();
// check if we are timed out
if ($current_time - $start_time > $timeout){
echo 'timeout! no new message!';
break;
}
// get latest message
$current_msg = get_msg();
// check if the message has been changed
if ($last_msg != $current_msg){
echo $current_msg;
break;
}
// sleep 1 sec
sleep(1);
}
分析上面的代碼, 其實原理就是在php中執行一個循環, 每次循環的時候去訪問服務器上的數據, 可以是數據庫也可以是一個文本文件,這里采用文本文件, 然后判斷文本文件的內容是否更新過,如果更新過則將數據返回給客戶端. 在循環當中我們放入了 sleep(1)函數讓代碼每次循環的時候暫停1秒鍾,這樣不至於過度損耗服務器CPU的資源.
下面是flex端的代碼<?xml version="1.0" encoding="utf-8"?>
// request object
private var req:URLRequest;
// loader object
private var loader:URLLoader;
// timer
private var timer:Timer;
[Bindable]
private var count:Number = 0;
// do some initializing
private function init():void{
req = new URLRequest('http://127.0.0.1:8080/long_polling.php');
loader = new URLLoader();
loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatusChange);
loader.addEventListener(Event.COMPLETE, onComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);
}
// update the second count
private function onTimer(event:TimerEvent):void{
count ++;
}
// start the request
private function startRequest():void{
count = 0;
loader.load(req);
timer.start();
this.txtLogs.text += 'Request started!\n';
}
// status changed
private function onStatusChange(event:HTTPStatusEvent):void{
this.txtLogs.text += 'Status changed to '+ String(event.status)+'\n';
}
// result returned
private function onComplete(event:Event):void{
this.txtLogs.text += 'Completed! Result is '+String(event.currentTarget.data)+'\n';
this.txtLogs.text += 'Start another request!\n';
this.startRequest();
}
// error handler
private function onIOError(event:IOErrorEvent):void{
this.txtLogs.text += 'IO Error: '+String(event)+'\n';
this.stopRequest();
}
// stop the request
private function stopRequest():void{
count = 0;
this.txtLogs.text += 'Request stopped!\n';
this.txtLogs.text += '<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>\n'
this.timer.stop();
this.loader.close();
}
]]>
我們新建一個flex項目, 然后在界面上放置4個空間, 兩個按鈕, 一個是開始請求, 一個是結束請求, 還有一個label用來顯示當前請求所花的時間, 最后再放一個TextArea來顯示log.
請求的原理也同樣簡單, 通過URLLoader和URLRequest來實現, 和普通的http請求並無兩樣.不過在一次請求獲取到結果的時候(走到onComplete()函數), 我們需要重新開始另外一次查詢,通過這種方式我們就可以模擬出一個類似與C/S架構的網絡連接, 而服務端上的任何更新則會自動推送到客戶端了.