熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

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

2013-11-23 19:29:12  來源: Java核心技術 

  CometProcessor 接口要求實現 event 方法這是用於 Comet 交互的一個生命周期方法Tomcat 將使用不同的 CometEvent 實例調用通過檢查 CometEvent 的 eventType可以判斷正處在生命周期的哪個階段當請求第一次傳入時即發生 BEGIN 事件READ 事件表明數據正在被發送只有當請求為 POST 時才需要該事件遇到 END 或 ERROR 事件時請求終止

  在清單 的例子中Servlet 使用一個 MessageSender 類發送數據這個類的實例是在 servlet 的 init 方法中在其自身的線程中創建並在 servlet 的 destroy 方法中銷毀的清單 顯示了 MessageSender

  清單 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){
  nnection = connection;
  notify();
  }
  public void send(String message) {
  synchronized (messages) {
  messagesadd(message);
  log(Message added #messages= + messagessize());
  messagesnotify();
  }
  }
  public void run() {
  while (running) {
  if (messagessize() == ) {
  try {
  synchronized (messages) {
  messageswait();
  }
  } catch (InterruptedException e) {
  // Ignore
  }
  }
  String[] pendingMessages = null;
  synchronized (messages) {
  pendingMessages = messagestoArray(new String[]);
  messagesclear();
  }
  try {
  if (connection == null){
  try{
  synchronized(this){
  wait();
  }
  } catch (InterruptedException e){
  // Ignore
  }
  }
  PrintWriter writer = connectiongetWriter();
  for (int j = ; j < pendingMessageslength; j++) {
  final String forecast = pendingMessages[j] + 
;
  writerprintln(forecast);
  log(Writing: + forecast);
  }
  writerflush();
  writerclose();
  connection = null;
  log(Closing connection);
  } catch (IOException e) {
  log(IOExeption sending message e);
  }
  }
  }
  }

  這個類基本上是樣板代碼與 Comet 沒有直接的關系但是有兩點要注意這個類含有一個 ServletResponse 對象回頭看看清單 中的 event 方法當事件為 BEGIN 時response 對象被傳入到 MessageSender 中在 MessageSender 的 run 方法中它使用 ServletResponse 將數據發送回客戶機注意一旦發送完所有排隊等待的消息後它將關閉連接這樣就實現了長輪詢如果要實現流風格的 Comet那麼需要使連接保持開啟但是仍然刷新數據

  回頭看清單 可以發現其中創建了一個 Weatherman 類正是這個類使用 MessageSender 將數據發送回客戶機這個類使用 Yahoo RSS feed 獲得不同地區的天氣信息並將該信息發送到客戶機這是一個特別設計的例子用於模擬以異步方式發送數據的數據源清單 顯示了它的代碼

  清單 Weatherman
 private class Weatherman implements Runnable{
  private final List zipCodes;
  private final String YAHOO_WEATHER = ;
  public Weatherman(Integer zips) {
  zipCodes = new ArrayList(zipslength);
  for (Integer zip : zips) {
  try {
  zipCodesadd(new URL(YAHOO_WEATHER + zip));
  } catch (Exception e) {
  // dont add it if it sucks
  }
  }
  }
  public void run() {
  int i = ;
  while (i >= ) {
  int j = i % zipCodessize();
  SyndFeedInput input = new SyndFeedInput();
  try {
  SyndFeed feed = inputbuild(new InputStreamReader(zipCodesget(j)
  openStream()));
  SyndEntry entry = (SyndEntry) feedgetEntries()get();
  messageSendersend(entryToHtml(entry));
  Threadsleep(L);
  } catch (Exception e) {
  // just eat it eat it
  }
  i++;
  }
  }
  private String entryToHtml(SyndEntry entry){
  StringBuilder html = new StringBuilder(
);
  htmlappend(entrygetTitle());
  htmlappend(
);
  htmlappend(entrygetDescription()getValue());
  return htmltoString();
  }
  }

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

  清單 客戶機 Comet 代碼

  <%@page contentType=text/html pageEncoding=UTF%> 
<!DOCTYPE HTML PUBLIC //WC//DTD HTML  Transitional//EN 
  > 
 
<html> 
  <head> 
    <meta httpequiv=ContentType content=text/html; charset=UTF> 
    <title>Comet Weather</title> 
    <SCRIPT TYPE=text/Javascript> 
      function go(){ 
        var url = //localhost:/WeatherServer/Weather 
        var request = new XMLHttpRequest(); 
        requestopen(GET url true); 
        requestsetRequestHeader(ContentTypeapplication/xjavascript;); 
        requestonreadystatechange = function() { 
          if (requestreadyState == ) { 
            if (requeststatus == ){ 
              if (requestresponseText) { 
                documentgetElementById(forecasts)innerHTML = 
requestresponseText; 
              } 
            } 
            go(); 
          } 
        }; 
        requestsend(null); 
      } 
    </SCRIPT> 
  </head> 
  <body> 
    <h>Rapid Fire Weather</h> 
    <input type=button onclick=go() value=Go!></input> 
    <div id=forecasts></div> 
  </body> 
</html> 

  該代碼只是在用戶單擊 Go 按鈕時開始長輪詢注意它直接使用 XMLHttpRequest 對象所以這在 Internet Explorer 中將不能工作您可能需要使用一個 Ajax 庫解決浏覽器差異問題除此之外惟一需要注意的是回調函數或者為請求的 onreadystatechange 函數創建的閉包該函數粘貼來自服務器的新的數據然後重新調用 go 函數

  現在我們看過了一個簡單的 Comet 應用程序在 Tomcat 上是什麼樣的有兩件與 Tomcat 密切相關的事情要做一是配置它的連接器二是在 Servlet 中實現一個特定於 Tomcat 的接口您可能想知道將該代碼 移植 到 Jetty 有多大難度接下來我們就來看看這個問題

  Jetty 和 Comet

  Jetty 服務器使用稍微不同的技術來支持 Comet 的可伸縮的實現Jetty 支持被稱作 continuations 的編程結構其思想很簡單請求先被暫停然後在將來的某個時間點再繼續規定時間到期或者某種有意義的事件發生都可能導致請求繼續當請求被暫停時它的線程被釋放

  可以使用 Jetty 的 orgmortbayutilajaxContinuationSupport 類為任何 HttpServletRequest 創建 orgmortbayutilajaxContinuation 的一個實例這種方法與 Comet 有很大的不同但是continuations 可用於實現邏輯上等效的 Comet清單 顯示清單 中的 weather servlet 移植 到 Jetty 後的代碼

  清單 Jetty Comet servlet
  public class JettyWeatherServlet extends HttpServlet {
  private MessageSender messageSender = null;
  private static final Integer TIMEOUT =  * ;
  public void begin(HttpServletRequest request HttpServletResponse response)
  throws IOException ServletException {
  requestsetAttribute(oet BooleanTRUE);
  requestsetAttribute(oettimeout TIMEOUT);
  messageSendersetConnection(response);
  Weatherman weatherman = new Weatherman( );
  new Thread(weatherman)start();
  }
  public void end(HttpServletRequest request HttpServletResponse response)
  throws IOException ServletException {
  synchronized (request) {
  requestremoveAttribute(oet);
  Continuation continuation = ContinuationSupportgetContinuation
  (request request);
  if (continuationisPending()) {
  continuationresume();
  }
  }
  }
  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();
  }
  @Override
  protected void service(HttpServletRequest request HttpServletResponse response)
  throws IOException ServletException {
  synchronized (request) {
  Continuation continuation = ContinuationSupportgetContinuation
  (request request);
  if (!continuationisPending()) {
  begin(request response);
  }
  Integer timeout = (Integer) requestgetAttribute
  (oettimeout);
  boolean resumed = continuationsuspend(timeout == null ?  :
  timeoutintValue());
  if (!resumed) {
  error(request response);
  }
  }
  }
  public void setTimeout(HttpServletRequest request HttpServletResponse response
  int timeout) throws IOException ServletException
  UnsupportedOperationException {
  requestsetAttribute(oettimeout new Integer(timeout));
  }
  }

  這裡最需要注意的是該結構與 Tomcat 版本的代碼非常類似beginreadend 和 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 規范

  Servlet 規范

  在此我們不深究 Servlet 規范的全部細節只看看 Comet servlet 如果在 Servlet 容器中運行可能會是什麼樣子注意 可能 二字該規范已經發布公共預覽版但在撰寫本文之際還沒有最終版因此清單 顯示的是遵從公共預覽規范的一個實現

  清單 Servlet Comet
 
  @WebServlet(asyncSupported=true asyncTimeout=)
  public class WeatherServlet extends HttpServlet {
  private MessageSender messageSender;
  // init and destroy are the same as other
  @Override
  protected void doGet(HttpServletRequest request HttpServletResponse response)
  throws ServletException IOException {
  AsyncContext async = requeststartAsync(request response);
  messageSendersetConnection(async);
  Weatherman weatherman = new Weatherman( );
  asyncstart(weatherman);;
  }
  }

  值得高興的是這個版本要簡單得多平心而論如果不遵從 Tomcat 的事件模型在 Jetty 中可以有類似的實現這種事件模型似乎比較合理很容易在 Tomcat 以外的容器(例如 Jetty)中實現只是沒有相關的標准

  回頭看看清單 注意它的標注聲明它支持異步處理並設置了超時時間startAsync 方法是 HttpServletRequest 上的一個新方法它返回新的 javaxservletAsyncContext 類的一個實例注意MessageSender 現在傳遞 AsynContext 的引用而不是 ServletResponse 的引用在這裡不應該關閉響應而是調用 AsyncContext 實例上的 complete 方法還應注意Weatherman 被直接傳遞到 AsyncContext 實例的 start 方法這樣將在當前 ServletContext 中開始一個新線程

  而且盡管與 Tomcat 或 Jetty 相比都有較大的不同但是修改相同風格的編程來處理 Servlet 規范提議的 API 並不是太難還應注意Jetty 是為實現 Servlet 而設計的目前處於 beta 狀態但是在撰寫本文之際它還沒有實現該規范的最新版本

  結束語

  Comet 風格的 Web 應用程序可以為 Web 帶來全新的交互性它為大規模地實現這些特性帶來一些復雜的挑戰但是領先的 Java Web 服務器正在為實現 Comet 提供成熟穩定的技術在本文中您看到了 Tomcat 和 Jetty 上當前風格的 Comet 的不同點和相似點以及正在進行的 Servlet 規范的標准化Tomcat 和 Jetty 使如今構建可伸縮的 Comet 應用程序成為可能並且明確了未來面向 Servlet 標准化的升級路線


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26942.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.