引言
在web應用普及的今天,用戶開始將更多的關鍵應用向Web遷移, 廣大Web應用開發者與推廣者在享受到了成功地喜悅。與同時很多用戶已經開始抱怨我們的Web應用總是那麼被動與遲鈍,何時才能讓他們的應用更加主動實時的,讓發生在服務端的事件第一時間內通知給他們。然而開發人員也不得不面對這樣的事實,在Web天生的無狀態與非連接制約下無法他們無法對應用的實時性更進一步的提升。
在近幾年的發展特別是Aajx的出現讓我們的web應用找到了新的興奮點,然而這仍然沒有解決前面提到的問題,難道我們真的無路可走了?
經過筆者的探索發現我們是可以實現基於web的實時主動通知的, 即采用AJax push技術,她可以讓我們web應用擁有前所未有激動人心的功能和使用體驗。筆者將通過本文帶領逐步大家去實現這個激動人心的體驗。筆者目的也非常簡單,目的在於為大家提供更多的參考資料 ,無論對錯希望本文能起到拋磚引玉的作用.如果能借此引發大家對Web實時推式通知技術的更為廣泛的討論我將感到萬分榮幸!
AJax push的廣闊前景
通知技術筆者把它們從概念上分為兩種:
被動的拉式通知技術和
主動的推式通知技術。這兩種說法目前網上已經有大量的文章,我在這裡只做簡單的介紹:
1、
被動的拉式通知技術又稱Pull方式如下圖
浏覽器
服務器
internet
定時請求
根據請求響應
圖:pull方式
拉方式需要客戶機不定時的檢查服務器已獲知是否發生新的事件或數據是否有變化,這種方式並不實時,但在web上比較容易實現。
2、
主動的推式通知技術又稱push方式如下圖
浏覽器
服務器
internet
發生事件主動發送
首次建立通信連接
事件
圖:push方式
推式客戶機與服務只需建立好連接之後,每當服務器有特殊事件發生時才通知客戶機,該方式實時性非常強,但目前在Web上實現較為復雜
這兩種方式後者有非常顯著的優點,而
AJax push就是需要在web上實現的後者的通信技術,如果
AJax push能被的完美實現,那麼基於web的IM軟件、基於Web的關鍵業務報警系統、基於web的實時監控系統、更智能人性的Web信息系統、甚至是基於web的遠程控制系統、等等都將可以實現,同時徹底擺脫客戶端部署與安裝,避免服務端的高負載。而webMsn、GMAIL這些系統中的很多被我們認為是不可思議的特性也能被任何一個web程序員輕松的開發出來。這是多麼美好的時刻,更加值得我們去期待。
突破觀念的束縛
Web應用的優點在於易於部署,但web是無狀態非連接的,從這個角度來看服務器無法對客戶端進行實時的推式通知,可能會有人對我說的不屑於顧,不是已經有很多系統都實現了web上的通知嗎?其實不然,目前實現web上動態通知的技術概括下來基本上有以下兩種方法:
1、
定期刷新法。定期刷新法又可以分為整體頁面間隔刷新和異步間隔刷新兩種方法。
a)
整體頁面間隔刷新法,該方法在早期聊天室中使用,該方法實現非常簡單,只需要在Html頭中加入如下代碼:
<META HTTP-EQUIV="refresh" CONTENT="10" URL="你的頁面">
該方法目前已經很少使用,主要因為如果現在網頁過於復雜加載時間長,如果頻繁刷新會對用戶造成非常差的體驗,同時會傳輸大量重復的網絡數據無疑加大了服務器及網絡的負擔。
b)
異步間隔刷新法,由於采用異步刷新方法,即使刷新平凡都不會造成頁面閃爍,同時減少了不必要的展現數據,是目前采用的較多的方法,實現上又分為AJax刷新即XMLHttp刷新和隱藏貞刷新。我給出簡單現示例如下:
AJax刷新
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.5.0");
function refreshUi()
{
XMLhttp.open("POST","你接收請求的頁面",true);
XMLhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");
XMLhttp.send(你好提交的數據);
var s=XMLhttp.responseText;//獲取服務器收到的數據
UpdateUi(s);//更新你的界面上的數據
}
setInterval(refreshUi,20);
服務段可以是一個可以是JSP,asp,serverlet,ASPx,等服務端處理程序,然後通過Response對象向客戶端輸出需要的數據即可
隱藏貞刷新
即在頁面上放入隱藏的FrameSet或則IFrame,然後通過對該貞的提交操作獲取數據然後刷新頁面
function refreshUi()
{
var hiddenForm= document.frames[1].document.forms[0];//獲取隱藏貞中的表單信息
hiddenForm.submit();//對表單進行提交
updateUi(hiddenForm);//對界面進行更新
}
setInterval(refreshUi,20);
但采用定期刷新法後不管是AJax提交還是隱藏貞提交都有共同的缺點是跟新不實時,刷新速度不能過快否則將嚴重影響客戶端或服務器的性能,同時很多客戶的數據在相當長的時間內一般不會更新,這樣的會造成無大量無意義的刷新,同時增大服務器的負載。但由於該方法實現簡單,所以大量的web應用程序采用了該方法實現通知技術,屬於非實時拉動(被動)式通知技術。
2、
客戶端插件法,通過為客戶段編寫第三方插件的形式嵌入到客戶端網頁中,然後同服務器建立通信連接實現即時通知技術,該技術有點可以實現服務端到客戶端的主動推式通知技術,目前主要是以ActiveX或是Java Applet的方式提供插件。缺點是實現技術復雜,需要讓客戶安裝插件,容易出現不兼容的情況,同時在目前網絡病毒泛濫的情況下,很多客戶端浏覽器上的安全策略是禁止安裝第三方的插件,這對部署和維護都帶來了相當大的難度。
從上面的技術實現上看目前這兩種方法目前缺點都非常明顯要麼非實時性,同時服務器負擔巨大;要麼安裝插件,沒有發揮web的優點。所以我們必須跳出常規方法尋找另外的方法,幸運的是經過筆者研究和嘗試基本上找到實現服務器實時主動通知客戶端的實現思路。
揭開神秘的面紗
事先說明,筆者在當前文章中只會提供實現的原理和參考代碼段,並不提供把該功能設計成組件的相關體系結構和類設計。而關於
AJax push組件的設計筆者正在編寫另一篇文章。
回到正題,我們先從客戶端談起,由於Web無狀態非連接的特性,如果我們要在web上實現push方式,那麼我們首要的是必須與服務器建立通信連接。然而我們要求是不安裝任何插件,那我們首先應當想到XmlHttpRequest對象,這個非常對,那麼我們如何它與服務器建立連接呢?很簡單,由於該通信連接不能阻塞浏覽器的主體運行所以我們不能采用同步請求方式,所以我們必須采用異步通信方式,而恰好得的XMLHttpRequest是提供的異步請求方式對於請求應答時常沒有什麼特別的限制,這正好符合我們與服務器建立長期連接的需求,也許微軟也打算在將來的某個時間提供對浏覽器push模式的支持。以下是建立長期通信連接的實例片斷:
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP.5.0");
function ConnectServer()
{
xmlhttp.open("POST","http://127.0.0.1:5565",true);//建立異步通信 XMLhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");
var m=XMLhttp.send("當前浏覽起的標識");
}
XMLhttp.onreadystatechange = function()
{
if(XMLhttp.readyState == 4)
{
//判斷返回值是否正常
if(XMLhttp.status == 200)
{
//執行你的方法
var s=XMLhttp.responseText;//獲取服務器發過來的信息
//執行你處理該事件的相關代碼
}
}
setTimeout(ConnectServer(),100);//重新建立下一次連接
}
ConnectServer();
注意,127.0.0.1是實例的服務器,在書寫程序時可以更換該地址,每次onreadystatechange被回調後應該建立另一次連接,應為每次服務器應答客戶機後客戶機就會斷開連接,所以每次服務器應答之後客戶機就應該建立新的連接以等待服務器的下一次通知。
現在客戶機已經能夠和服務器建立長久的連接,那麼服務器該如何通知客戶機呢?好的,現在我們就來解決該問題,當客戶機一個XMLhttp請求發送到服務器後,服務器接收到該請求不立即應答,而將該請求掛起,直到服務器產生需要通知客戶機的事件時才應答客戶機。而如何掛起該請求了?有兩種方案:
1、 是在web服務器上實現一個Serverlet(Java)或httpHandle(.Net)來接收該請求,當請求的調用到來時我們可以阻塞該線程上的調用,直到服務器產生通知客戶機的事件。
2、 自行實現一個簡單的httpserver來接收應答並處理。即用一個線程來監聽指定端口,當請求到達時將用於應答的套接字(socket)存儲在內存中的列表中,當服務器有事件通知時從列表中檢索出對應的套接字並應答。
由於第一種方案會阻塞線程,由於web服務器應答各種請求的線程數是有限的所以第一種方案會帶來性能損失和不穩定因素。所以第二種方案才是可行的。基於Java的參考代碼如下:
接收XMLhttp請求的代碼片斷
Public static HashMap<String, Socket> clIEntSockets=new HashMap<String, Socket>();
private boolean serverStarted=true;
.....
.....
.....
Java.Net.ServerSocket ss;
ss = new Java.Net.ServerSocket(5565);//設置監聽端口
while(serverStarted) //循環應答
{
try {
Java.lang.Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
Java.Net.Socket s =ss.accept();
byte[] b =new byte[s.getInputStream().available()] ;
s.getInputStream().read(b);
String requestStr=new String(b,"utf-8");
//從請求字符串中分析出客戶端發來的標示符
String clIEntId=parseRequestStr(requestStr);
clientSockets.put(clIEntId,s);
}
……..
當服務器發生事件需要通知客戶段時的代碼片斷
Java.Net.Socket s=clientSockets.get(clIEntId);//從列表中檢索出需要的客戶段套接字
if(s==null)
return;
java.io.PrintWriter p=new Java.io.PrintWriter(s.getOutputStream());
p.println("HTTP/1.1 200OK");
p.println("Content-Type:text/Html; charset:utf-8");
p.println("Content-Length:"+msg.length());//msg為服務器要發到客戶端的信息
p.println();
p.println(msg);
p.flush();
s.getOutputStream().flush();
s.getOutputStream().close();
s.close();
clientSockets.remove(clIEntId);//移出發送的Socket
好了倒此為止服務器主動通知客戶機
AJax push技術的基本實現原理和參考代碼段已經給出,相信大家根據筆者所給出的技術要點舉一反三實現出更好的推式技術。
結束語
最後我還是要補充的說幾句,筆者在文章中只是給出的關鍵實現,但在實際中應用該技術還需要考慮很多,我在這裡舉幾個要考慮的比較重要點:
1、 關於客戶段異常,如果連接失敗如何最省資源的自動再次連接,如何檢測連接已經斷開
2、 服務器需要對列表中的套接字進行定期驗證以保證列表中的連接可用
3、 很多消息要連續通知客戶機是對消息考慮隊列的處理
好了我就說這裡,如果大家有更多的問題和建議請E-mail聯系我,希望大家看過本文後也能向別人問起“你的應用今天
AJax push了嗎?”