反向推送技術現在非常流行, 而長輪詢是實現反向推送的關鍵技術之一.
//如果要轉載本文請註明出處,免的出現版權紛爭,我不喜歡看到那種轉載了我的作品卻不註明出處的人 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端的代碼,很簡單
<?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"?>
<![CDATA[
// 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架構的網路連接, 而服務端上的任何更新則會自動推送到用戶端了.