2008 年的夏天,偶然在網上閑逛的時候發現了 Comet 技術,人云亦云間,姑且認為它是由 Dojo 的 Alex Russell 在 2006 年提出。在閱讀了大量的資料后,萌發出寫篇 blog 來說明什么是 Comet 的想法。哪知道這個想法到了半年后的今天才提筆,除了繁忙的工作拖延外,還有 Comet 本身帶來的困惑。
Comet 能帶來生產力的提升是有目共睹的。現在假設有 1000 個用戶在使用某軟件,輪詢 (polling) 和 Comet 的設定都是 1s 、 10s 、 100s 的潛伏期,那么在相同的潛伏期內, Comet 所需要的帶寬更小,如下圖:
不僅僅是在帶寬上的優勢,每個用戶所真正感受到的響應時間(潛伏期)更短,給人的感覺也就更加的實時,如下圖:
再引用一篇 IBMDW 上的譯文《使用 Jetty 和 Direct Web Remoting 編寫可擴展的 Comet 應用程序》,其中說到:吸引人們使用 Comet 策略的其中一個優點是其顯而易見的高效性。客戶機不會像使用輪詢方法那樣生成煩人的通信量,并且事件發生后可立即發布給客戶機。
上面一遍一遍的說到 Comet 技術的優勢,那么我們可以替換現有的技術結構了?不幸的是,近半年的擦邊球式的關注使我對 Comet 的理解越發的糊涂,甚至有人說 Comet 這個名詞已被濫用。去年的一篇博文,《 The definition of Comet? 》使 Comet 更加撲朔迷離,甚至在維基百科上大家也對準確的 Comet 定義產生爭論。還是等牛人們爭論清楚再修改維基百科吧,在這里我想還是引用維基百科對 Comet 的定義:服務器推模式 (HTTP server push 、 streaming) 以及長輪詢 (long polling) ,這兩種模式都是 Comet 的實現。
除了對 Comet 的準確定義尚缺乏有效的定論外, Comet 還存在不少技術難題,隨著 Tomcat 6 、 Jetty 6 的發布,他們基于 NIO 各自實現了異步 Servlet 機制。有興趣的看官可以分別實現這兩個容器的 Comet ,至少我還沒玩轉。
在編寫服務器端的代碼上面,我很困惑, http://tomcat.apache.org/tomcat-6.0-doc/aio.html 這里演示了如何在 Tomcat 6 中實現異步 Servlet ;我們再把目光換到 Jetty 6 上,還是前面提到的那篇 IBMDW 譯文,如果你和我一樣無聊,可以下載那邊文章的 sample 代碼。我驚奇的發現每個廠商對異步 Servlet 的封裝是不同的,一個傻傻的問題:我的 Comet 服務器端的代碼可移植么?至今我還在問這個問題!好吧,業界有規范么?有當然有,不過看起來有些爭論會發生——那就是 Servlet 3.0 規范 (JSR-315) , Servlet 3.0 正在公開預覽,它明確的支持了異步 Servlet ,《 Servlet 3.0 公開預覽版引發爭論》,又讓我高興不起來了:“來自 RedHat 的 Bill Burke 寫的一篇博文,其中他批評了 Jetty 6 中的異步 servlet 實現 ......Greg Wilkins 宣布他致力于 Servlet 3.0 異步 servlet 的一個實現 ...... 雖然還需要更多測試,但是這個代碼已經實現了基本的異步行為,不需要很復雜的重新分發請求或者前遞方法。我相信這代表了 3.0 的合理折中方案。在我們從 3.0 的簡單子集里獲得經驗之后,如果需要更多的特性,可以添加到 3.1 中 ........” 。牛人們還在做最佳范例,口水仗也還要繼續打,看來要嘗到 Comet 的甜頭是很困難的。 STOP !我已經不想再分析如何寫客戶端的代碼了,什么 dojo 、 extJs 、 DWR 、 ZK....... 都有自己的實現。我認為這一切都要等 Servelt 3.0 正式發布以后,如何編寫客戶端代碼才能明朗點。
現在拋開繞來繞去的爭執吧,既然 Ajax+Servlet 實現 Comet 很困難,何不換個思維呢。我這里倒是有個小小的 sample ,說明如何在 Adobe BlazeDS 中實現長輪詢模式。關于 BlazeDS ,可以在這里找到些信息。為了說明什么是長輪詢,首先來看看什么是輪詢,既在一定間隔期內由 web 客戶端發起請求到服務器端取回數據,如下圖所示:
?????????????????????????
至于輪詢的缺點,在前面的論述中已有覆蓋,至于優點大家可以 google 一把,我覺得最大的優點就是技術上很好實現,下面是個 Ajax 輪詢的例子,這是一個簡單的聊天室,首先是 chat.html 代碼,想必這些代碼網上一抓就一大把,支持至少 IE6 、 IE7 、 FF3 瀏覽器,讓人煩心的是亂碼問題,在傳遞到 Servlet 之前要 encodeURI 一下 :
接下來是 Servlet ,如果你是用的 Tomcat ,在這里注意下編碼問題,否則又是亂碼,另外我使用 LinkedList 實現了一個隊列,該隊列的最大長度是 30 ,也就是最多能保存 30 條聊天信息,舊的將被丟棄,另外新的客戶端進來后能讀取到最近的信息:
打開瀏覽器,實驗下效果,將就用吧,稍微有些延遲。還是看看長輪詢吧,長輪詢有三個顯著的特征:
1. 服務器端會阻塞請求直到有數據傳遞或超時才返回。
2. 客戶端響應處理函數會在處理完服務器返回的信息后,再次發出請求,重新建立連接。
3. 當客戶端處理接收的數據、重新建立連接時,服務器端可能有新的數據到達;這些信息會被服務器端保存直到客戶端重新建立連接,客戶端會一次把當前服務器端所有的信息取回。
下圖很好的說明了以上特征:
?????????????????????????????
既然關注的是 BlazeDS 如何實現長輪詢,那么有必要稍微了解下。 BlazeDS 包含了兩個重要的服務,進行遠端方法調用的 RPC service 和傳遞異步消息的 Messaging Service ,我們即將探討的長輪詢屬于 Messaging Service 。 Messaging Service 使用 producer consumer 模式來分別定義消息的發送者 (producer) 和消費者 (consumer) ,具體到 Flex 代碼,有 Producer 和 Consumer 兩個組件對應。在廣闊的互聯網上有很多 BlazeDS 入門的中文教材,我就不再廢話了。假設你已經裝好 BlazeDS ,打開 WEB-INF/flex/services-config.xml 文件,在 channels 節點內加一個 channel 聲明長輪詢頻道,關于 channel 和 endpoint 請參閱 About channels and endpoints 章節:
如何實現長輪詢的玄機就在上面的 properties 節點內, polling-enabled = true ,打開輪詢模式; wait-interval-millis = 6000 服務器端的潛伏期,也就是服務器會保持與客戶端的連接,直到超時或有新消息返回(恩,看來這就是長輪詢了); polling-interval-millis = 0 表示客戶端請求服務器端的間隔期, 0 表示沒有任何的延遲; max-waiting-poll-requests = 150 表示服務器能承受的最大長連接用戶數,超過這個限制,新的客戶端就會轉變為普通的輪詢方式(至于這個數值最大能有多大,這和你的 web 服務器設置有關了,而 web 服務器的最大連接數就和操作系統有關了,這方面的話題不在本文內探討)。
其實這樣設置之后,長輪詢的代碼已經實現了一半了。恩,不錯!看起來比異步 Servlet 實現起來簡單多了。不過要實現和之前 Ajax 輪詢一樣的效果,還得實現自己的 ServiceAdapter ,這就是 Adapter 的用處:
接下來注冊該 Adapter ,打開 WEB-INF/flex/messaging-config.xml 文件,在 adapters 節點內加入一個 adapter-definition 來聲明自定義 Adapter :
接著定義一個 destination ,以便 Flex 客戶端能訂閱聊天室,組裝好之前定義的長輪詢頻道和 adapter :
服務器端就算搞定了,接著搞定 Flex 那邊的代碼吧,灰常灰常的簡單。先到 Building your client-side application 學習如何創建和 BlazeDS 通訊的 Flex 項目。然后在 chat.mxml 中寫下:
之前我們說到的 Producer 和 Consumer 組件在這里出現了,由于我們要訂閱的是同一個聊天室,所以 destination="chat" ,而 Consumer 組件則注冊回調函數 messageHandler() ,處理異步消息的到來。當打開這個聊天客戶端的時候,在 creationComplete 初始化完成后,立即進行 consumer.subscribe() ,其實接下來應該就能直接收到服務器端回饋的聊天記錄了,但是我沒仔細學習如何監聽客戶端的訂閱,所以在這里我直接 send() 了一個空消息以便服務器端能回饋已有的聊天記錄,接下來我就不用再講解了,都能看懂。
現在打開瀏覽器,感受下長輪詢的效果吧。不過遇到個問題,如果 FF 同時開兩個聊天窗口,第二個打開的會有延遲感, IE 也是,按照牛人們的說法,當一個瀏覽器開兩個以上長連接的時候才會有延遲感,不解。 BlazeDS 的長輪詢也不是十全十美,有人說它不是真正的“實時” The Truth About BlazeDS and Push Messaging ,隨即引發出口水仗,里面提到的 RTMP 協議在 2009 年 1 月已開源,相信以后 BlazeDS 會更“實時”;接著又有人說 BlazeDS 不是非阻塞式的,這個問題后來也沒人來對應。罷了,畢竟BlazeDS才開源不久,容忍一下吧。最后,我想說的是,不論 BlazeDS 到底有什么問題,至少實現起來是輕松的,在 Servlet 3.0 沒發布之前,是個不錯的選擇。
請注意!引用、轉貼本文應注明原作者:Rosen Jiang 以及出處: http://www.aygfsteel.com/rosen
Powered by: BlogJava Copyright © Rosen