Ajax push技術核心揭秘 引言 在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了嗎?” |