使用Java实现Comet风格的Web应用(二)
轉 : http://java.csdn.net/page/ded4d480-49ad-4a67-b75b-b4dff7cc1f0d
CometProcessor 接口要求實現 event 方法。這是用于 Comet 交互的一個生命周期方法。Tomcat 將使用不同的 CometEvent 實例調用。通過檢查 CometEvent 的 eventType,可以判斷正處在生命周期的哪個階段。當請求第一次傳入時,即發生 BEGIN 事件。READ 事件表明數據正在被發送,只有當請求為 POST 時才需要該事件。遇到 END 或 ERROR 事件時,請求終止。
在清單 2 的例子中,Servlet 使用一個 MessageSender 類發送數據。這個類的實例是在 servlet 的 init 方法中在其自身的線程中創建,并在 servlet 的 destroy 方法中銷毀的。清單 3 顯示了 MessageSender.
清單 3. MessageSender
private class MessageSender implements Runnable {protected boolean running = true; protected final ArrayList messages = new ArrayList(); private ServletResponse connection; private synchronized void setConnection(ServletResponse connection){this.connection = connection; notify(); }public void send(String message) {synchronized (messages) {messages.add(message); log("Message added #messages=" + messages.size()); messages.notify(); }}public void run() {while (running) {if (messages.size() == 0) {try {synchronized (messages) {messages.wait(); }} catch (InterruptedException e) {// Ignore}}String[] pendingMessages = null; synchronized (messages) {pendingMessages = messages.toArray(new String[0]); messages.clear(); }try {if (connection == null){try{synchronized(this){wait(); }} catch (InterruptedException e){// Ignore}}PrintWriter writer = connection.getWriter(); for (int j = 0; j < pendingMessages.length; j++) {final String forecast = pendingMessages[j] + "
"; writer.println(forecast); log("Writing:" + forecast); }writer.flush(); writer.close(); connection = null; log("Closing connection"); } catch (IOException e) {log("IOExeption sending message", e); }}}}
?
這個類基本上是樣板代碼,與 Comet 沒有直接的關系。但是,有兩點要注意。這個類含有一個 ServletResponse 對象。回頭看看清單 2 中的 event 方法,當事件為 BEGIN 時,response 對象被傳入到 MessageSender 中。在 MessageSender 的 run 方法中,它使用 ServletResponse 將數據發送回客戶機。注意,一旦發送完所有排隊等待的消息后,它將關閉連接。這樣就實現了長輪詢。如果要實現流風格的 Comet,那么需要使連接保持開啟,但是仍然刷新數據。
回頭看清單 2 可以發現,其中創建了一個 Weatherman 類。正是這個類使用 MessageSender 將數據發送回客戶機。這個類使用 Yahoo RSS feed 獲得不同地區的天氣信息,并將該信息發送到客戶機。這是一個特別設計的例子,用于模擬以異步方式發送數據的數據源。清單 4 顯示了它的代碼。
清單 4. Weatherman
private class Weatherman implements Runnable{private final List zipCodes; private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p="; public Weatherman(Integer... zips) {zipCodes = new ArrayList(zips.length); for (Integer zip : zips) {try {zipCodes.add(new URL(YAHOO_WEATHER + zip)); } catch (Exception e) {// dont add it if it sucks}}}public void run() {int i = 0; while (i >= 0) {int j = i % zipCodes.size(); SyndFeedInput input = new SyndFeedInput(); try {SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(j).openStream())); SyndEntry entry = (SyndEntry) feed.getEntries().get(0); messageSender.send(entryToHtml(entry)); Thread.sleep(30000L); } catch (Exception e) {// just eat it, eat it}i++; }}private String entryToHtml(SyndEntry entry){StringBuilder html = new StringBuilder(" "); html.append(entry.getTitle()); html.append(" "); html.append(entry.getDescription().getValue()); return html.toString(); }}
?
這個類使用 Project Rome 庫解析來自 Yahoo Weather 的 RSS feed.如果需要生成或使用 RSS 或 Atom feed,這是一個非常有用的庫。此外,這個代碼中只有一個地方值得注意,那就是它產生另一個線程,用于每過 30 秒鐘發送一次天氣數據。最后,我們再看一個地方:使用該 Servlet 的客戶機代碼。在這種情況下,一個簡單的 JSP 加上少量的 JavaScript 就足夠了。清單 5 顯示了該代碼。
清單 5. 客戶機 Comet 代碼
?
"http://www.w3.org/TR/html4/loose.dtd"> function go(){ var url = "http://localhost:8484/WeatherServer/Weather" var request = new XMLHttpRequest(); request.open("GET", url, true); request.setRequestHeader("Content-Type","application/x-javascript; "); request.onreadystatechange = function() { if (request.readyState == 4) { if (request.status == 200){ if (request.responseText) { document.getElementById("forecasts").innerHTML = request.responseText; } } go(); } }; request.send(null); }
?
Rapid Fire Weather
該代碼只是在用戶單擊 Go 按鈕時開始長輪詢。注意,它直接使用 XMLHttpRequest 對象,所以這在 Internet Explorer 6 中將不能工作。您可能需要使用一個 Ajax 庫解決瀏覽器差異問題。除此之外,惟一需要注意的是回調函數,或者為請求的 onreadystatechange 函數創建的閉包。該函數粘貼來自服務器的新的數據,然后重新調用 go 函數。
現在,我們看過了一個簡單的 Comet 應用程序在 Tomcat 上是什么樣的。有兩件與 Tomcat 密切相關的事情要做:一是配置它的連接器,二是在 Servlet 中實現一個特定于 Tomcat 的接口。您可能想知道,將該代碼 “移植” 到 Jetty 有多大難度。接下來我們就來看看這個問題。
Jetty 和 Comet
Jetty 服務器使用稍微不同的技術來支持 Comet 的可伸縮的實現。Jetty 支持被稱作 continuations 的編程結構。其思想很簡單。請求先被暫停,然后在將來的某個時間點再繼續。規定時間到期,或者某種有意義的事件發生,都可能導致請求繼續。當請求被暫停時,它的線程被釋放。
可以使用 Jetty 的 org.mortbay.util.ajax.ContinuationSupport 類為任何 HttpServletRequest 創建 org.mortbay.util.ajax.Continuation 的一個實例。這種方法與 Comet 有很大的不同。但是,continuations 可用于實現邏輯上等效的 Comet.清單 6 顯示清單 2 中的 weather servlet “移植” 到 Jetty 后的代碼。
清單 6. Jetty Comet servlet
public class JettyWeatherServlet extends HttpServlet {private MessageSender messageSender = null; private static final Integer TIMEOUT = 5 * 1000; public void begin(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {request.setAttribute("org.apache.tomcat.comet", Boolean.TRUE); request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT); messageSender.setConnection(response); Weatherman weatherman = new Weatherman(95118, 32408); new Thread(weatherman).start(); }public void end(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {synchronized (request) {request.removeAttribute("org.apache.tomcat.comet"); Continuation continuation = ContinuationSupport.getContinuation(request, request); if (continuation.isPending()) {continuation.resume(); }}}public void error(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {end(request, response); }public boolean read(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {throw new UnsupportedOperationException(); }@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {synchronized (request) {Continuation continuation = ContinuationSupport.getContinuation(request, request); if (!continuation.isPending()) {begin(request, response); }Integer timeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout"); boolean resumed = continuation.suspend(timeout == null ? 10000 :timeout.intValue()); if (!resumed) {error(request, response); }}}public void setTimeout(HttpServletRequest request, HttpServletResponse response,int timeout) throws IOException, ServletException,UnsupportedOperationException {request.setAttribute("org.apache.tomcat.comet.timeout", new Integer(timeout)); }}
?
這里最需要注意的是,該結構與 Tomcat 版本的代碼非常類似。begin、read、end 和 error 方法都與 Tomcat 中相同的事件匹配。該 Servlet 的 service 方法被覆蓋為在請求第一次進入時創建一個 continuation 并暫停該請求,直到超時時間已到,或者發生導致它重新開始的事件。上面沒有顯示 init 和 destroy 方法,因為它們與 Tomcat 版本是一樣的。該 servlet 使用與 Tomcat 相同的 MessageSender.因此不需要修改。注意 begin 方法如何創建 Weatherman 實例。對這個類的使用與 Tomcat 版本中也是完全相同的。甚至客戶機代碼也是一樣的。只有 servlet 有更改。雖然 servlet 的變化比較大,但是與 Tomcat 中的事件模型仍是一一對應的。
希望這足以鼓舞人心。雖然完全相同的代碼不能同時在 Tomcat 和 Jetty 中運行,但是它是非常相似的。當然,JavaEE 吸引人的一點是可移植性。大多數在 Tomcat 中運行的代碼,無需修改就可以在 Jetty 中運行,反之亦然。因此,毫不奇怪,下一個版本的 Java Servlet 規范包括異步請求處理(即 Comet 背后的底層技術)的標準化。 我們來看看這個規范:Servlet 3.0 規范。
Servlet 3.0 規范
在此,我們不深究 Servlet 3.0 規范的全部細節,只看看 Comet servlet 如果在 Servlet 3.0 容器中運行,可能會是什么樣子。注意 “可能” 二字。該規范已經發布公共預覽版,但在撰寫本文之際,還沒有最終版。因此,清單 7 顯示的是遵從公共預覽規范的一個實現。
清單 7. Servlet 3.0 Comet
@WebServlet(asyncSupported=true, asyncTimeout=5000)public class WeatherServlet extends HttpServlet {private MessageSender messageSender; // init and destroy are the same as other@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {AsyncContext async = request.startAsync(request, response); messageSender.setConnection(async); Weatherman weatherman = new Weatherman(95118, 32444); async.start(weatherman); ; }}
?
值得高興的是,這個版本要簡單得多。平心而論,如果不遵從 Tomcat 的事件模型,在 Jetty 中可以有類似的實現。這種事件模型似乎比較合理,很容易在 Tomcat 以外的容器(例如 Jetty)中實現,只是沒有相關的標準。
回頭看看清單 7,注意它的標注聲明它支持異步處理,并設置了超時時間。startAsync 方法是 HttpServletRequest 上的一個新方法,它返回新的 javax.servlet.AsyncContext 類的一個實例。注意,MessageSender 現在傳遞 AsynContext 的引用,而不是 ServletResponse 的引用。在這里,不應該關閉響應,而是調用 AsyncContext 實例上的 complete 方法。還應注意,Weatherman 被直接傳遞到 AsyncContext 實例的 start 方法。這樣將在當前 ServletContext 中開始一個新線程。
而且,盡管與 Tomcat 或 Jetty 相比都有較大的不同,但是修改相同風格的編程來處理 Servlet 3.0 規范提議的 API 并不是太難。還應注意,Jetty 7 是為實現 Servlet 3.0 而設計的,目前處于 beta 狀態。但是,在撰寫本文之際,它還沒有實現該規范的最新版本。
結束語
Comet 風格的 Web 應用程序可以為 Web 帶來全新的交互性。它為大規模地實現這些特性帶來一些復雜的挑戰。但是,領先的 Java Web 服務器正在為實現 Comet 提供成熟、穩定的技術。在本文中,您看到了 Tomcat 和 Jetty 上當前風格的 Comet 的不同點和相似點,以及正在進行的 Servlet 3.0 規范的標準化。Tomcat 和 Jetty 使如今構建可伸縮的 Comet 應用程序成為可能,并且明確了未來面向 Servlet 3.0 標準化的升級路線。
總結
以上是生活随笔為你收集整理的使用Java实现Comet风格的Web应用(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爱可生开源mysql_爱可生开源社区官网
- 下一篇: GitModel|Task04|随机模拟