使用Java實現Comet風格的Web應用(二)

來源:互聯網
上載者:User

轉 : 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 {<br />  protected boolean running = true;<br />  protected final ArrayList messages = new ArrayList();<br />  private ServletResponse connection;<br />  private synchronized void setConnection(ServletResponse connection){<br />  this.connection = connection;<br />  notify();<br />  }<br />  public void send(String message) {<br />  synchronized (messages) {<br />  messages.add(message);<br />  log("Message added #messages=" + messages.size());<br />  messages.notify();<br />  }<br />  }<br />  public void run() {<br />  while (running) {<br />  if (messages.size() == 0) {<br />  try {<br />  synchronized (messages) {<br />  messages.wait();<br />  }<br />  } catch (InterruptedException e) {<br />  // Ignore<br />  }<br />  }<br />  String[] pendingMessages = null;<br />  synchronized (messages) {<br />  pendingMessages = messages.toArray(new String[0]);<br />  messages.clear();<br />  }<br />  try {<br />  if (connection == null){<br />  try{<br />  synchronized(this){<br />  wait();<br />  }<br />  } catch (InterruptedException e){<br />  // Ignore<br />  }<br />  }<br />  PrintWriter writer = connection.getWriter();<br />  for (int j = 0;  j < pendingMessages.length;  j++) {<br />  final String forecast = pendingMessages[j] + "<br />";<br />  writer.println(forecast);<br />  log("Writing:" + forecast);<br />  }<br />  writer.flush();<br />  writer.close();<br />  connection = null;<br />  log("Closing connection");<br />  } catch (IOException e) {<br />  log("IOExeption sending message", e);<br />  }<br />  }<br />  }<br />  }<br />

 

這個類基本上是樣板代碼,與 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{<br />  private final List zipCodes;<br />  private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p=";<br />  public Weatherman(Integer... zips) {<br />  zipCodes = new ArrayList(zips.length);<br />  for (Integer zip : zips) {<br />  try {<br />  zipCodes.add(new URL(YAHOO_WEATHER + zip));<br />  } catch (Exception e) {<br />  // dont add it if it sucks<br />  }<br />  }<br />  }<br />  public void run() {<br />  int i = 0;<br />  while (i >= 0) {<br />  int j = i % zipCodes.size();<br />  SyndFeedInput input = new SyndFeedInput();<br />  try {<br />  SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(j)<br />  .openStream()));<br />  SyndEntry entry = (SyndEntry) feed.getEntries().get(0);<br />  messageSender.send(entryToHtml(entry));<br />  Thread.sleep(30000L);<br />  } catch (Exception e) {<br />  // just eat it, eat it<br />  }<br />  i++;<br />  }<br />  }<br />  private String entryToHtml(SyndEntry entry){<br />  StringBuilder html = new StringBuilder("<br />");<br />  html.append(entry.getTitle());<br />  html.append("<br />");<br />  html.append(entry.getDescription().getValue());<br />  return html.toString();<br />  }<br />  }

 

這個類使用 Project Rome 庫解析來自 Yahoo Weather 的 RSS feed.如果需要產生或使用 RSS 或 Atom feed,這是一個非常有用的庫。此外,這個代碼中只有一個地方值得注意,那就是它產生另一個線程,用於每過 30 秒鐘發送一次天氣資料。最後,我們再看一個地方:使用該 Servlet 的客戶機代碼。在這種情況下,一個簡單的 JSP 加上少量的 JavaScript 就足夠了。清單 5 顯示了該代碼。

清單 5. 客戶機 Comet 代碼
 

 

 "http://www.w3.org/TR/html4/loose.dtd"> <br /> <br /> <br />   <br />     <br />     <br />     <br />      function go(){ <br />        var url = "http://localhost:8484/WeatherServer/Weather" <br />        var request = new XMLHttpRequest();  <br />        request.open("GET", url, true);  <br />        request.setRequestHeader("Content-Type","application/x-javascript; ");  <br />        request.onreadystatechange = function() { <br />          if (request.readyState == 4) { <br />            if (request.status == 200){ <br />              if (request.responseText) { <br />                document.getElementById("forecasts").innerHTML = <br />request.responseText;  <br />              } <br />            } <br />            go();  <br />          } <br />        };  <br />        request.send(null);  <br />      } <br />

 

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 {<br />  private MessageSender messageSender = null;<br />  private static final Integer TIMEOUT = 5 * 1000;<br />  public void begin(HttpServletRequest request, HttpServletResponse response)<br />  throws IOException, ServletException {<br />  request.setAttribute("org.apache.tomcat.comet", Boolean.TRUE);<br />  request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);<br />  messageSender.setConnection(response);<br />  Weatherman weatherman = new Weatherman(95118, 32408);<br />  new Thread(weatherman).start();<br />  }<br />  public void end(HttpServletRequest request, HttpServletResponse response)<br />  throws IOException, ServletException {<br />  synchronized (request) {<br />  request.removeAttribute("org.apache.tomcat.comet");<br />  Continuation continuation = ContinuationSupport.getContinuation<br />  (request, request);<br />  if (continuation.isPending()) {<br />  continuation.resume();<br />  }<br />  }<br />  }<br />  public void error(HttpServletRequest request, HttpServletResponse response)<br />  throws IOException, ServletException {<br />  end(request, response);<br />  }<br />  public boolean read(HttpServletRequest request, HttpServletResponse response)<br />  throws IOException, ServletException {<br />  throw new UnsupportedOperationException();<br />  }<br />  @Override<br />  protected void service(HttpServletRequest request, HttpServletResponse response)<br />  throws IOException, ServletException {<br />  synchronized (request) {<br />  Continuation continuation = ContinuationSupport.getContinuation<br />  (request, request);<br />  if (!continuation.isPending()) {<br />  begin(request, response);<br />  }<br />  Integer timeout = (Integer) request.getAttribute<br />  ("org.apache.tomcat.comet.timeout");<br />  boolean resumed = continuation.suspend(timeout == null ? 10000 :<br />  timeout.intValue());<br />  if (!resumed) {<br />  error(request, response);<br />  }<br />  }<br />  }<br />  public void setTimeout(HttpServletRequest request, HttpServletResponse response,<br />  int timeout) throws IOException, ServletException,<br />  UnsupportedOperationException {<br />  request.setAttribute("org.apache.tomcat.comet.timeout", new Integer(timeout));<br />  }<br />  }<br />

 

這裡最需要注意的是,該結構與 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)<br />  public class WeatherServlet extends HttpServlet {<br />  private MessageSender messageSender;<br />  // init and destroy are the same as other<br />  @Override<br />  protected void doGet(HttpServletRequest request, HttpServletResponse response)<br />  throws ServletException, IOException {<br />  AsyncContext async = request.startAsync(request, response);<br />  messageSender.setConnection(async);<br />  Weatherman weatherman = new Weatherman(95118, 32444);<br />  async.start(weatherman); ;<br />  }<br />  }<br />

 

值得高興的是,這個版本要簡單得多。平心而論,如果不遵從 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 標準化的升級路線。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.