摘要:Comet技術(shù)通過(guò)實(shí)現服務(wù)器推(server push)來(lái)解決AJAX需要定時(shí)頻繁發(fā)送請求的問(wèn)題,從而給Web實(shí)時(shí)系統帶來(lái)了全新的交互性。本文分析了Comet技術(shù)的兩種實(shí)現方式:長(cháng)輪詢(xún)方式 (long-polling)和流方式(streaming),并通過(guò)一個(gè)服務(wù)器推送隨即數的實(shí)例,展示了使用Comet技術(shù)開(kāi)發(fā)Java Web實(shí)時(shí)系統的開(kāi)發(fā)方法和步驟。
關(guān)鍵詞:Comet;AJAX;服務(wù)器推送;Web實(shí)時(shí)系統
HTTP協(xié)議是互聯(lián)網(wǎng)上大量信息交換的基礎,其特點(diǎn)是,它是基于請求—響應模式的無(wú)狀態(tài)的單向協(xié)議,即必須由客戶(hù)端發(fā)起一個(gè)請求建立連接,服務(wù)器接收請求,把數據返回給客戶(hù)端,然后釋放連接。下一次,再由客戶(hù)端發(fā)起另一次請求,重復上述過(guò)程。服務(wù)器始終處于“被動(dòng)”地位。
HTTP協(xié)議這一特點(diǎn),既成就了它的成功,也導致了它的局限性。服務(wù)器處理請求的經(jīng)典模式是一個(gè)線(xiàn)程處理一個(gè)連接,結束之后,關(guān)閉該連接,釋放線(xiàn)程以服務(wù)于其他請求。只要響應速度足夠快, 那么我們可以以相對較少的服務(wù)器為數量龐大的用戶(hù)提供服務(wù)。這非常適合于傳統的Web應用,比如:搜索引擎、內容管理系統和電子商務(wù)網(wǎng)站等。然而,這種方式并不能滿(mǎn)足有實(shí)時(shí)性要求的應用的需求,很多應用都需要服務(wù)器能實(shí)時(shí)地將更新的信息傳送到客戶(hù)端,而無(wú)須客戶(hù)端發(fā)出請求。例如,新聞標題、證券報價(jià)和拍賣(mài)行情等。
在Web的早期,人們通過(guò)在HTML頭部加入META元信息來(lái)實(shí)現HTML自動(dòng)刷新。該標記指示瀏覽器每隔一定的時(shí)間間隔刷新一次頁(yè)面。這不僅帶來(lái)糟糕的用戶(hù)體驗,而且是一種低效的做事方式。因為如果沒(méi)有新的數據,該頁(yè)面就沒(méi)必要刷新;如果頁(yè)面只存在小范圍內的變化,該頁(yè)面就沒(méi)有必要全部刷新。
AJAX(Asynchronous JavaScript and XML,異步JavaScript和XML)的出現改變了上述情況。Ajax的工作原理相當于在客戶(hù)和服務(wù)器之間加了—個(gè)中間層,使客戶(hù)請求與服務(wù)器響應異步化。并不是所有的請求都提交給服務(wù)器,像—些數據驗證和數據處理等都交給AJAX引擎自己來(lái)做,只有確定需要從服務(wù)器讀取新數據時(shí)再由AJAX引擎代為向服務(wù)器提交請求。使用Ajax的最大優(yōu)點(diǎn)就是能在不刷新整個(gè)頁(yè)面的前提下維護數據,使得Web應用程序更為迅捷地響應用戶(hù)交互,并避免了在網(wǎng)絡(luò )上發(fā)送那些沒(méi)有改變的信息。然而,AJAX仍然受限于Web請求/響應模式的弱點(diǎn),使得服務(wù)器不能推送實(shí)時(shí)動(dòng)態(tài)的Web數據。
1 Comet技術(shù)實(shí)現方式[1]
Comet技術(shù)被稱(chēng)為反AJAX(Reverse AJAX)技術(shù),它通過(guò)實(shí)現服務(wù)器推(server push)來(lái)解決AJAX需要定時(shí)頻繁發(fā)送請求的問(wèn)題。通過(guò)Comet,客戶(hù)端所需要的響應信息不再需要主動(dòng)地去索取,而是在服務(wù)器端以事件 (Event)的形式推至客戶(hù)端。
Comet技術(shù)的實(shí)現方式有兩種:長(cháng)輪詢(xún)方式(long-polling)和流方式(streaming)。
長(cháng)輪詢(xún):HTTP的連接保持,服務(wù)器端會(huì )阻塞請求,直到服務(wù)器端有一個(gè)事件觸發(fā)或者到達超時(shí)??蛻?hù)端在收到響應后再次發(fā)出請求,重新建立連接。通過(guò)這種方式,服務(wù)器可以在數據可用的任何時(shí)候將數據“推”到客戶(hù)端。因為這種方案基于A(yíng)JAX,請求異步發(fā)出,無(wú)須安裝插件,IE、Mozilla FireFox都支持。
流方式:在流方式中,服務(wù)器推數據返回客戶(hù)端,但不關(guān)閉連接,連接始終保持,直到超時(shí),超時(shí)后通知客戶(hù)端重新建立連接,并關(guān)閉原來(lái)的連接。
在長(cháng)輪詢(xún)方式下,客戶(hù)端是在XMLHttpRequest的readystate為4(即數據傳輸結束)時(shí)調用回調函數,進(jìn)行信息處理。當 readystate為4時(shí),數據傳輸結束,連接已經(jīng)關(guān)閉。Mozilla Firefox提供了對流方式的支持,即readystate為3時(shí)(數據仍在傳輸中),客戶(hù)端可以讀取數據,從而無(wú)須關(guān)閉連接,就能讀取處理服務(wù)器端返回的信息。IE在readystate為3時(shí),不能讀取服務(wù)器返回的數據,目前IE不支持流方式。
不管是長(cháng)輪詢(xún)還是流,請求都需要在服務(wù)器上存在一段較長(cháng)時(shí)間,因此Comet被稱(chēng)為“基于HTTP長(cháng)連接的服務(wù)器推技術(shù)”。這打破了每個(gè)請求一個(gè)線(xiàn)程的模型。這個(gè)模型顯然對Comet不適用。 Java對此提出了非阻塞IO(non-blocking IO)解決方案,Java通過(guò)它的NIO庫提供非阻塞IO處理Comet。
傳統的阻塞式IO,每個(gè)連接必須要開(kāi)一個(gè)線(xiàn)程來(lái)處理,您始終從一個(gè)線(xiàn)程中讀取流直到整個(gè)流完成,然后關(guān)閉連接。因此阻塞式IO對大量并發(fā)的短生命周期連接不會(huì )造成問(wèn)題。而非阻塞IO處理連接是異步的。當某個(gè)連接發(fā)送請求到服務(wù)器,服務(wù)器把這個(gè)連接請求當作一個(gè)請求“事件”,并把這個(gè)“事件”分配給相應的函數處理。我們把這個(gè)處理函數放到線(xiàn)程中去執行,執行完就把線(xiàn)程歸還。這樣一個(gè)線(xiàn)程就可以異步地處理多個(gè)事件。
為了獲得事件通知,我們需要一個(gè)機制,它只在需要讀時(shí)才讀,需要寫(xiě)時(shí)才寫(xiě),但又保持連接打開(kāi)以迅速響應發(fā)生的事件。為了方便這個(gè)過(guò)程,就要用到NIO,它已是1.4版本以后的Java語(yǔ)言的一部分。
2 使用Java開(kāi)發(fā)Comet風(fēng)格的Web應用
支持Comet的Java Web開(kāi)源服務(wù)器有Tomcat 6.0.14和Jetty 6.1.14,它們的實(shí)現方法各不相同。下面我們以Tomcat為例來(lái)說(shuō)明開(kāi)發(fā)Comet風(fēng)格的Web應用的步驟[2]。
本例以流方式實(shí)現了一個(gè)Comet應用。服務(wù)器每隔一定的時(shí)間間隔產(chǎn)生一個(gè)0~9之間的隨機數,將數據推送到客戶(hù)端??蛻?hù)端接收并顯示。
第一,要下載和安裝Tomcat6.X(本文截稿時(shí),Tomcat最新版本是6.0.24)。
第二,為了使用Comet,要求服務(wù)器支持NIO,所以要修改Tomcat配置文件conf/server.xml, 即啟用異步版本的IO連接器,這個(gè)非常關(guān)鍵。如下所示:
第三,該項目需要Comet的API支持,Tomcat6自帶的Comet API包為catalina.jar,在Tomcat安裝目錄下的lib目錄中。
第四,編寫(xiě)Servlet。通過(guò)servlet實(shí)現CometProcessor接口。這個(gè)接口要求實(shí)現event()方法,在配置的 Http11NioProtocol調用event()方法來(lái)處理請求,而不是doGet或doPost。最基本的支持Comet的servlet實(shí)現如程序清單1所示。
在event()方法中,分別處理連接開(kāi)始(BEGIN)、新數據可用(READ),連接結束(END),或出錯等事件。Comet允許針對不同的事件指定不同的連接超時(shí)。這意味著(zhù)可以給常規的請求設置很短的生命周期,但是對于響應長(cháng)連接請求的機制,可以將這個(gè)生命周期延長(cháng)至幾分鐘。
論文出處(作者):
TestComet Servlet中,在連接開(kāi)始時(shí)首先設置連接超時(shí)為60秒,接著(zhù)啟動(dòng)一個(gè)推送數據的線(xiàn)程。該線(xiàn)程的實(shí)現類(lèi)為RandomSender,如程序清單2所示。請注意,這個(gè)類(lèi)含有一個(gè) ServletResponse對象?;仡^看看清單1中的event()方法,當事件為BEGIN時(shí),response對象被傳入到 RandomSender中。RandomSender的run()使用ServletResponse將數據發(fā)送回客戶(hù)機。因為要實(shí)現流風(fēng)格的 Comet,所以不能關(guān)閉連接。而要使連接保持開(kāi)啟。如果要實(shí)現長(cháng)輪詢(xún),則一旦發(fā)送完所有消息后,就要關(guān)閉連接。
第五,編寫(xiě)客戶(hù)端。在客戶(hù)端,發(fā)出AJAX請求。請求和常規請求差不多。程序清單3測試了最基本的AJAX請求,它基于XMLHttpRequest, 能夠很好地響應來(lái)自Comet服務(wù)器的事件??蛻?hù)端在readystate為3時(shí)(數據仍在傳輸中)讀取數據,從而無(wú)須關(guān)閉連接,就能讀取處理服務(wù)器端返回的數據,將它顯示在瀏覽器上。
第六,運行程序。首先進(jìn)行部署,為了使程序正常運行,先要刪除本應用的lib目錄下的catalina.jar,如果不這么做,會(huì )出現異常:java.lang.ClassCastException: org.apache.catalina.util.DefaultAnnotationProcessor cannot be cast to org.apache.AnnotationProcessor。
最后,重啟Tomcat6,用FireFox瀏覽器調用主頁(yè)面index.jsp,就可以看到隨機數不斷地涌現。
3 結束語(yǔ)
采用Comet技術(shù)實(shí)現的Java Web實(shí)時(shí)系統帶來(lái)了全新的交互性,目前Java Web服務(wù)器正在為實(shí)現Comet提供成熟、穩定的技術(shù),不久的將來(lái),Comet將成為Servlet 3.0和JavaEE6的標準的一部分。
參考文獻
[1] GALPIN M. Developing with Comet and Java [EB/OL].(2009-05-26)[2009-08-18].http://www.ibm.com/developerworks/web/library/wa-cometjava/index.html.
[2] Apache Software Foundation. Advanced IO and Tomcat [EB/OL]. [2009-09-05].http://tomcat.apache.org/tomcat-6.0-doc/aio.html.

