李賽男 ,余金山
(1.華僑大學 計算機科學與計算學院,福建 廈門 361021;2.華僑大學 計算機科學與計算學院,福建 泉州 362011)
一些實時監(jiān)控、在線即時通信、RIA的Web應用等系統(tǒng)狀態(tài)發(fā)生變化時需要及時反饋數(shù)據(jù)給客戶端。這種實時交互可以利用推技術來實現(xiàn)。隨著Ajax技術的興起,系統(tǒng)的實時性也出現(xiàn)新的解決方案——服務器推送(Comet[1])。服務器推送不同于以往的推技術,它是基于Web平臺、通過Http長連接、無須安裝插件的服務器向客戶端推送數(shù)據(jù)的純?yōu)g覽器技術。
服務器推送是通過保持客戶端與服務器端的Http連接,當服務器端狀態(tài)發(fā)送變化時,將數(shù)據(jù)發(fā)送給客戶端瀏覽器,由瀏覽器負責顯示這些數(shù)據(jù),然后連接繼續(xù)被維持,等待下一次服務器推送[2]。
Pushlet是一種輕量級的服務器推送開源框架,相對于其他流行的推送框架如Dojo+Jetty、DWR等,Pushlet有以下優(yōu)點:
(1)通用性。Pushlet框架是基于 Servlet實現(xiàn)的,可以在任何一種支持Servlet的服務器上運行。
(2)簡單易用。Pushlet提供了一組簡明用戶接口,使用戶能方便地使用Pushlet框架,而且客戶端使用Java Script技術,可以很容易與已有的Web應用程序集成。
(3)可配置性。Pushlet使用屬性文件配置框架中的各種組件,用戶可以用自己開發(fā)的組件替換Pushlet提供的組件。
(4)擴展性。對于比較復雜的推送應用,用戶可以自由擴展Pushlet框架。
(5)協(xié)議簡單。Pushlet的客戶端和服務器端交互采用的協(xié)議是經過簡化的bayeux協(xié)議。
但是Pushlet主要關注Comet服務器端的實現(xiàn),相對其在數(shù)據(jù)傳輸和客戶端方面設計實現(xiàn)上存在一些不足:
(1)Pushlet使用的數(shù)據(jù)傳輸格式只能存儲結構簡單的消息數(shù)據(jù)。
(2)客戶端只能連接一個Pushlet服務器。
針對上述不足,本文提出使用JSON格式存儲服務器發(fā)送的數(shù)據(jù),在創(chuàng)建Pushlet服務器連接方面,使用OOP思想把連接封裝成類,以便多次創(chuàng)建連接對象連接多個Pushlet服務器。
Pushlet是Comet的開源框架,它提供基于Ajax長輪詢方式和基于iframe的流方式兩種的服務器推送實現(xiàn)[3]。整個框架涉及服務器端以及客戶端的實現(xiàn)。服務器端采用Servlet技術監(jiān)聽客戶端請求,客戶端采用JavaScript技術并實現(xiàn)跨瀏覽器實現(xiàn)。Pushlet服務器端框架和客戶端框架基于 Pushlet協(xié)議,協(xié)議示意圖分別如圖1、圖 2所示。
圖1 Ajax長輪詢模式的Pushlet交互協(xié)議
圖2 streaming模式的Pushlet交互協(xié)議
Pushlet協(xié)議[4]組成:
(1)會話管理:客戶端發(fā)送 “join”請求建立一個Pushlet會話,以后服務器通過這個會話管理該客戶端的所有請求。客戶端發(fā)送“l(fā)isten”請求來監(jiān)聽并準備接收服務器端的數(shù)據(jù)。Pushlet還提供了一個快捷請求“joinlisten”合并這兩步請求??蛻舳税l(fā)送的“l(fā)eave”請求用來斷開會話。
(2)主題訂閱:客戶端通過“subscribe”請求向服務器訂閱一個或多個主題,告訴服務器如果這個主題的狀態(tài)發(fā)生改變了就告訴我??蛻舳艘部梢酝ㄟ^“unsubscribe”來取消一個或多個主題的訂閱。
(3)消息發(fā)布:客戶端可以通過發(fā)送“publish”請求讓服務器廣播或多播它的消息。
(4)數(shù)據(jù)推送:服務端通過發(fā)送“data”消息向客戶端推送數(shù)據(jù)。
(5)心跳消息:在連接快超時時,服務器向發(fā)送“heartbeat”消息說明會話還存活,以防瀏覽器請求超時。
(6)刷新消息:在 Ajax長輪詢的模式下,服務器每次有數(shù)據(jù)返回消息給客戶端、發(fā)送心跳信號、返回listen-ack時,都必須再發(fā)送一條refresh消息給客戶端,告訴客戶端在多長時間后發(fā)送“refresh”請求重新建立連接以保持長Http連接。
為加強協(xié)議的可靠性,客戶端向服務器端發(fā)送的控制請求,服務器都會返回相應的ack消息。Pushlet靠兩條Http連接與服務器交互:一條是專門發(fā)送控制請求;另外一條則是專門接收數(shù)據(jù)的長連接,這個長連接是以“l(fā)isten”請求開始的。
Pushlet整體框架如圖3所示[5]。
圖3 Pushlet整體框架圖
Pushlet框架中基于Ajax長輪詢的客戶端框架從服務器端獲得的是XML格式的數(shù)據(jù),其格式是<event消息字段="字段值".../>,具體示例如下:
Pushlet雖然使用了具有強大數(shù)據(jù)描述能力的XML來存儲推送的數(shù)據(jù),可是它只是簡單地利用元素的屬性來存儲鍵/值對格式的消息數(shù)據(jù),類似HTTP GET請求的 QueryString, 如 :p_event=join-listen-ack&p_mode=pull&p_time=1321791777&p_id=qegagopapu。 這種 鍵/值 對的數(shù)據(jù)格式只能存儲簡單信息,不易存儲額外的復雜數(shù)據(jù)。如上述例子中從服務器獲得的數(shù)據(jù)相當于服務器使用XML格式傳送Pushlet協(xié)議的頭部信息,而復雜的實際數(shù)據(jù)很難使用這種鍵/值的格式存儲。
對于上述Pushlet的局限性,可以使用JSON進行數(shù)據(jù)傳輸。按照Ajax的定義,原則上應該采用傳統(tǒng)的XML數(shù)據(jù)傳輸格式??墒荴ML是一種結構化的文檔,在服務器和客戶端均需要進行手工解析[6]。這種方案不僅存在解析時的復雜性問題,而且對于不同瀏覽器存在兼容性問題。此外,XML使用的標簽會產生大量的數(shù)據(jù)冗余減低數(shù)據(jù)傳輸效率。相對地JSON是一種輕量級的數(shù)據(jù)交換格式,它是JavaScript規(guī)范的子集,本質上就是JavaScript的復合對象。所以JSON易于被瀏覽器解析和生成,也可以表示非常復雜的數(shù)據(jù)。故本文選擇JSON作為數(shù)據(jù)傳輸格式,把服務器傳送的數(shù)據(jù)使用JSON格式傳輸,具體如下:
基于Ajax長輪詢的Pushlet客戶端框架還有一個局限就是一個客戶端只能和一個Pushlet服務交互。這個客戶端框架使用JavaScript函數(shù)式編程,只能定義Window的一個全局變量建立一個會話與Pushlet服務端交互。為使客戶端可以創(chuàng)建多個會話與多個Pushlet服務端交互,本文使用面向對象方法封裝已有的框架創(chuàng)建會話類,這樣就可以定義多個會話對象創(chuàng)建多個連接。
改進后客戶端框架中各個類實例之間的交互如圖4所示。
(1)Session: 會 話 管 理 類 , 包 含 Pushlet服 務 端 的URI、會話ID、會話狀態(tài)。用戶可以通過Session對象進行命令請求。對于接收到消息后的處理函數(shù),則可以通過配置對象在創(chuàng)建Session實例時指定。
(2)Check:狀態(tài)驗證類,驗證當前會話狀態(tài)保證正確的協(xié)議時序。
(3)Control:消息路由類,根據(jù)接收到消息分發(fā)給相應的消息處理函數(shù)。
(4)Ajax:異步通信類,封裝 Ajax功能直接與服務器端通信。
(5)PushletEvent:Pushlet事件類,負責解析事件消息和生成事件。
圖4 改進后的客戶端框架時序圖
使用JSON進行數(shù)據(jù)交互,相應地在服務器端也需要有一個JSON數(shù)據(jù)發(fā)送器。Pushlet的服務器端框架提供了一個專門用于向客戶端發(fā)送數(shù)據(jù)的ClientAdapter接口,這個JSON數(shù)據(jù)發(fā)送器就必須實現(xiàn)這個ClientAdapter接口。另外還需在Command類的createClientAdapter方法中增加一個判斷來生成JSON數(shù)據(jù)發(fā)送器的實例。
以往大部分的Web投票系統(tǒng)都只是實現(xiàn)最基本的功能:發(fā)起投票和投票并評論,如QQ群社區(qū)的群投票。在有些決策性投票中,用戶希望能看到投票的實時情況。為此用戶必須時刻刷新頁面才能看到投票的最新情況。本文應用改進的Pushlet框架技術實現(xiàn)的Web投票系統(tǒng),是在基本的投票應用功能上增加顯示實時投票情況的功能。
投票Web應用主要分5個模塊:發(fā)起投票模塊、投票查詢模塊、投票模塊、登錄/注冊模塊、網(wǎng)站狀態(tài)模塊。該應用只有注冊用戶才可以發(fā)起投票,投票可以分為匿名投票和實名投票。投票查詢又分一般查詢、過期投票查詢、未過期投票查詢、用戶參與的投票和用戶發(fā)起的投票。投票模塊需要顯示各個候選項的投票比例和用戶的投票留言。網(wǎng)站狀態(tài)模塊則是顯示網(wǎng)站的基本信息,如注冊的用戶、總發(fā)起投票的個數(shù)等。本應用的實現(xiàn)使用了SSH2框架,網(wǎng)站前臺實現(xiàn)使用Ext JS框架,服務器推送部分使用Pushlet框架。投票Web應用的總體架構如圖5所示。
圖5 投票Web應用的架構
為進一步說明改進的Pushlet框架技術的具體應用,下面給出系統(tǒng)的某些部分的實現(xiàn)。系統(tǒng)的實時性需求有如下兩點:(1)當有用戶注冊或發(fā)起投票后,該應用的基本信息就會改變,這時服務器就需要推送該應用最新的基本信息給客戶端;(2)當用戶投票成功后,該項投票的候選項的票數(shù)比例會改變,而且也會有新的投票留言,這時服務器也需要推送該項投票的最新信息給客戶端。
Pushlet通過主題來管理所有需要推送的數(shù)據(jù)。主題采用具有層次的樹形結構,上層主題包含下層主題,類似文件系統(tǒng)的目錄,如/system/time、/system/tem 就 表 示兩個主題。如果訂閱主題為“/system”,則表示同時訂閱了/system下的所有子主題 (/system/time和/system/tem);如果訂閱主題為“/”的根主題,則表示訂閱了服務器里所有的主題。
該應用中主要有兩個大主題:網(wǎng)站最新基本信息和投票最新信息,分別用“/vote/basic”和“/vote/info”表示主題。網(wǎng)站基本信息包括多個項目,如用戶注冊人數(shù)、發(fā)起投票總個數(shù)等,這些信息按項目名稱劃分主題:“/vote/basic/項目名稱”。用戶可能同時進行多項投票,投票最新信息按照投票 ID劃分主題:“/vote/info/投票 ID”。每個客戶端都必須訂閱“/vote/basic”主題,在打開一個投票表單時需要訂閱“/vote/info/該投票ID”主題。
圖6是客戶端與服務器直接交互的具體情況。當有新用戶注冊時,服務器會向其他客戶端推送網(wǎng)站的注冊用戶數(shù)的基本信息。當有用戶確定投票時,服務器也會推送投票的最新情況。
該應用的客戶端是基于Ext JS框架并使用經典的MVC模式和命名空間來組織代碼。Ext控件的配置和布局放在視圖包,Ext的事件處理函數(shù)和Pushlet客戶端框架放在控制包,Ext控件的Store和數(shù)據(jù)模型Model放在數(shù)據(jù)模型包。為與Ext異步調用的技術一致,Pushlet框架的客戶端選用基于Ajax長輪詢的方式。這里使用JSON數(shù)據(jù)方式傳送,引用的框架庫文件是改進后的框架庫文件。該應用的服務器端則需要在調用相關邏輯層服務后調用Pushlet的分發(fā)函數(shù),向客戶端推送數(shù)據(jù)。具體服務器端組件間的交互如圖7所示。
圖6 客戶端與服務器的交互
圖7 服務器端各組件之間的交互
Pushlet框架在該應用中的集成步驟如下:
(1) 把 pushlet.jar復制到 WebRootWEB-INFlib下,并添加進classpath。
(2)把pushlet.properties和sources.properties復制到WebRootWEB-INF下。pushlet.properties是Pushlet的配置文件,本應用中沒有用到數(shù)據(jù)源,需要把sources.activate的值設為false。
(3)把Pushlet改進后的客戶端框架的文件Ajax-pus hlet-client-json.js復制到WebRoot下的一個lib文件中。
本文對Pushlet框架的一些不足做了改進。但是Pushlet的實現(xiàn)方式使服務器必須維護很多連接,因此對服務器性能要求很高。對此服務器可以選擇經過性能改進的Jetty服務器。Pushlet這種長連接的推技術僅適合用于中小型的應用。本文中的投票Web應用也是基于一定群體用戶的投票行為。
[1]Alex Russell.Comet: low latency data for the browser[EB/OL].[2006-06].http://alex.dojotoolkit.org/?p=545.
[2]孫清國,朱瑋,劉華軍,等.Web應用中的服務器推送技術研究綜述[J].計算機系統(tǒng)應用,2008(11):116-120.
[3]陳航,趙方.基于服務器推送技術和XMPP的Web IM系統(tǒng)實現(xiàn)[J].計算機工程與設計,2010,31(5):925-928.
[4]BROECKE J V D.Pushlets-protocol specification[EB/OL].[2006-05-25].http://www.pushlets.com/doc/protocol-all.html.
[5]yxw246.Pushlet 2.0.3源碼分析 (服務器端)[EB/OL].[2008-05-08].http://blog.csdn.net/yxw246/article/details/2418255.
[6]張濤,黃強,毛磊雅,等.一個基于 JSON的對象序列化算法[J].計算機工程與應用,2007,43(15):98-100.