簡介: 自 JSR 315 規範(即Servlet 3.0)的草案公開發布以來,最新一代Servlet 規範的各類新特性被愈來愈多的
開發人員所關注。規範中提到的一系列高級目標:如可插拔的Web 框架、便捷開發特性、加強安全性支持等都使人期
待。但其中關注程度最高的,毫無疑問是異步Servlet。本文將詳細介紹Comet 風格應用的實現方式,以及Servlet
3.0 中的異步處理特性在Comet 風格程序中的實際應用。web
概述
做爲 Java EE 6 體系中重要成員的JSR 315 規範,將Servlet API 最新的版本從2.5 提高到了3.0,這是近10 年來
Servlet 版本號最大的一次升級,這次升級中引入了若干項令開發人員興奮的特性,如:
1. 可插拔的Web 架構(Web framework pluggability)。
2. 經過Annotations 代替傳統web.xml 配置文件的EOD 易於開發特性(ease of development)。
3. Serlvet 異步處理支持。
4. 安全性提高,如Http Only Cookies、login/logout 機制。
5. 其它改進,如文件上傳的直接支持等。
其中,在開源社區中討論得最多的就是Servlet 異步處理的支持,所謂Servlet 異步處理,包括了非阻塞的輸入/輸
出、異步事件通知、延遲request 處理以及延遲response 輸出等幾種特性。這些特性大多並不是JSR 315 規範首次提
出,譬如非阻塞輸入/輸出,在Tomcat 6.0 中就提供了Advanced NIO 技術以便一個Servlet 線程能處理多個Http
Request,Jetty、GlassFish 也曾經有過相似的支持。可是使用這些Web 容器提供的高級特性時,由於現有的
Servlet API 沒有對這類應用的支持,因此都必須引入一些Web 容器專有的類、接口或者Annotations,致使使用了這
部分高級特性,就必須與特定的容器耦合在一塊兒,這對不少項目來講都是沒法接受的。所以JSR 315 將這些特性寫入
規範,提供統一的API 支持後,這類異步處理特性才真正具有普遍意義上的實用性,只要支持Servlet 3.0 的 Web 容
器,就能夠不加修改的運行這些Web 程序。瀏覽器
JSR 315 中的Servlet 異步處理系列特性在不少場合都有用武之地,但人們最早看到的,是它們在「服務端推」
(Server-Side Push)方式—— 也稱爲Comet 方式的交互模型中的價值。在JCP(Java Community Process)網
站上提出的JSR 315 規範目標列表,關於異步處理這個章節的標題就直接定爲了「Async and Comet support」(異步
與Comet 支持)。安全
「Comet 技術」、「服務端推技術(Server-Side Push)」、「反向Ajax 技術」這幾個名稱說的是同一件事情,可能
您已經據說過其中的一項或者幾項。但沒據說過也沒有關係,一句話就足以表達它們所有的意思:「在沒有客戶端請
求的狀況下,服務端向客戶端發送數據」。
這句話聽起來很簡單很好理解,可是任何一個長期從事B/S 應用程序開發的程序都清楚,這實現起來並不簡單,甚至
很長一段時間內,人們認爲這是並不可能的。由於這種作法徹底不符合傳統基於HTTP 協議的交互思想:只有基於
Socket 層次的應用才能作到Server 和Client 端雙方對等通信,而基於HTTP 的應用中,Server 只是對來自Client
的請求進行迴應,不關心客戶端的狀態,不主動向客戶端請求信息,所以Http 協議被稱爲無狀態、單向性協議,這種
交互方式稱爲Request-Response 交互模型。
無狀態、單向的經典Request-Response 交互模型有不少優勢,譬如高效率、高可伸縮等。對於被動響應用戶請求爲
主的應用,像CMS、MIS、ERP 等很是適合,可是對於另一些須要服務端主動發送的需求,像聊天室(用戶不發言
的時候也須要把其它用戶的發言傳送回來)、日誌系統(客戶端沒有請求,當服務端有日誌輸出時主動發送到客戶
端)則處理起來很困難,或者說這類應用根本不適合使用經典的Request-Response 交互模型來處理。當「不適合」
與「有需求」同時存在時,人們就開始不斷尋找突破這種限制的方法。服務器
Comet 實現的方法
1.簡單輪詢
最先期的Web 應用中,主要經過JavaScript 或者Meta HTML 標籤等手段,定時刷新頁面來檢測服務端的
變化。顯然定時刷新頁面服務端仍然在被動響應客戶端的請求,只不過客戶端的請求是連續、頻繁的,讓用戶
看起來產生有服務端自動將信息發過來的錯覺。這種方式簡單易行,但缺陷也很是明顯:可能大部分請求都是
無心義的,由於服務端期待的事件沒有發生,實際上並無須要發送的信息,而不得不重複的迴應着頁面上所
有內容給瀏覽器;另外就是當服務端發生變化時,並不能「實時」的返回,刷新的間隔過短,產生很大的性能
浪費,間隔太長,事件通知又可能晚於用戶指望的時間到達。
當絕大部分瀏覽器提供了XHR(XmlHttpRequest)對象支持後,Ajax 技術出現並迅速流行,這一階段作的輪
詢就沒必要每次都返回都返回整個頁面中全部的內容,若是服務端沒有事件產生,只須要返回極少許內容的
http 報文體。Ajax 能夠節省輪詢傳輸中大量的帶寬浪費,但它沒法減小請求的次數,所以Ajax 實現的簡單輪
詢仍然有輪詢的侷限性,對其缺陷只能必定程度緩解,而沒法達到質變。架構
2.長輪詢(混合輪詢)
長輪詢與簡單輪詢的最大區別就是鏈接時間的長短:簡單輪詢時當頁面輸出完鏈接就關閉了,而長輪詢通常會
保持30 秒乃至更長時間,當服務器上期待的事件發生,將會馬上輸出事件通知到客戶端,接着關閉鏈接,同
時創建下一個鏈接開始一次新的長輪詢。
長輪詢的實現方式優點在於當服務端期待事件發生,數據便當即返回到客戶端,期間沒有數據返回,再較長的
等待時間內也沒有新的請求發生,這樣可讓發送的請求減小不少,而事件通知的靈敏度卻大幅提升到幾乎是
「實時」的程度。框架
3.Comet 流(Forever Frame)
Comet 流是按照長輪詢的實現思路進一步發展的產物。令長輪詢將事件通知發送回客戶端後再也不關閉鏈接,
而是一直保持直到超時事件發生才從新創建新的鏈接,這種變體咱們就稱爲Comet 流。客戶端可使用
頁碼,2/10
XmlHttpRequest 對象中的readyState 屬性來判斷是Receiving 仍是Loaded。Comet 流理論上可使用一
個連接來處理若干次服務端事件通知,更進一步節省了發送到服務端的請求次數。
不管是長輪詢仍是Comet 流,在服務端和客戶端都須要維持一個比較長時間的鏈接狀態,這一點在客戶端不算什麼太
大的負擔,可是服務端是要同時對多個客戶端服務的,按照經典Request-Response 交互模型,每個請求都佔用一
個Web 線程不釋放的話,Web 容器的線程則會很快消耗殆盡,而這些線程大部分時間處於空閒等待的狀態。這也就
是爲何Comet 風格服務很是期待異步處理的緣由,但願Web 線程不須要同步的、一對一的處理客戶端請求,能作
到一個Web 線程處理多個客戶端請求。
實戰Servlet 異步處理
當前已經有很多支持Servlet API 3.0 的 Web 容器,如GlassFish v三、Tomcat 7.0、Jetty 8.0 等,在本文撰寫時,
Tomcat 7 和Jetty 8 都仍然處於測試階段,雖然支持Servlet 3.0,可是提供的樣例代碼仍然是與容器耦合的NIO 實
現,GlassFish v3 提供的樣例(玻璃魚聊天室)則是徹底標準的Servlet 3.0 實現,若是讀者須要作找參考樣例,不妨
優先查看GlassFish 的example 目錄。本文後一部分會提供另一個更具有實用性的例子「Web 日誌系統」做爲
Servlet API 3.0 的實戰演示進行講解。異步