1. 程式人生 > >基於AJAX的長輪詢(long-polling)方式實現COMET例子

基於AJAX的長輪詢(long-polling)方式實現COMET例子

什麼是 Comet?

解釋: Alex Russell ( Dojo Toolkit 的專案 Lead )稱這種基於 HTTP 長連線、無須在瀏覽器端安裝外掛的 “ 伺服器推 ” 技術為 “Comet” 。

有兩種實現 Comet 應用的實現模型,目前主要討論的是基於 AJAX 的長輪詢 (long-polling) 方式

例子如下:

Servlet實現類:TestComet

Java程式碼  收藏程式碼
  1. <span style="font-family: tahoma,arial,helvetica,sans-serif; font-size: small;">public class
     TestComet extends HttpServlet implements CometProcessor {  
  2.     private static final long serialVersionUID = 1L;  
  3.     // 傳送器  
  4.     private MessageSender messageSender = null;  
  5.     private static final Integer TIMEOUT = 60 * 1000;  
  6.     @Override  
  7.     public void destroy() {  
  8.         messageSender.stop();  
  9.         messageSender = null
    ;  
  10.     }  
  11.     @Override  
  12.     public void init() throws ServletException {  
  13.         System.out.println("--init-----------------");  
  14.         // 初始化傳送器  
  15.         messageSender = new MessageSender();  
  16.         Thread messageSenderThread = new Thread(messageSender, "MessageSender["  
  17.                 + getServletContext().getContextPath() + "]"
    );  
  18.         messageSenderThread.setDaemon(true);  
  19.         // 啟動傳送執行緒  
  20.         messageSenderThread.start();  
  21.     }  
  22.     public void event(final CometEvent event) throws IOException,  
  23.             ServletException {  
  24.         System.out.println("--event-----------------");  
  25.         // 獲取事件對應的REQUEST 和 RESPONSE  
  26.         HttpServletRequest request = event.getHttpServletRequest();  
  27.         HttpServletResponse response = event.getHttpServletResponse();  
  28.         if (event.getEventType() == CometEvent.EventType.BEGIN) {  
  29.             request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);  
  30.             log("Begin for session: " + request.getSession(true).getId());  
  31.             // 注入RESPONSE  
  32.             messageSender.setConnection(response);  
  33.             Weatherman weatherman = new Weatherman(messageSender, 9511832408);  
  34.             new Thread(weatherman).start();  
  35.         } else if (event.getEventType() == CometEvent.EventType.ERROR) {  
  36.             log("Error for session: " + request.getSession(true).getId());  
  37.             event.close();  
  38.         } else if (event.getEventType() == CometEvent.EventType.END) {  
  39.             log("End for session: " + request.getSession(true).getId());  
  40.             event.close();  
  41.         } else if (event.getEventType() == CometEvent.EventType.READ) {  
  42.             throw new UnsupportedOperationException(  
  43.                     "This servlet does not accept data");  
  44.         }  
  45.     }  
  46. }  
  47. </span>  

 資訊傳送器:

Java程式碼  收藏程式碼
  1. <span style="font-family: tahoma,arial,helvetica,sans-serif; font-size: small;">public class MessageSender implements Runnable {  
  2.     // 標誌位  
  3.     protected boolean running = true;  
  4.     // 資訊列表  
  5.     protected final ArrayList<String> messages = new ArrayList<String>();  
  6.     // HTTP RESPONSE  
  7.     private ServletResponse connection;  
  8.     // 注入HTTP RESPONSE  
  9.     public synchronized void setConnection(ServletResponse connection) {  
  10.         this.connection = connection;  
  11.         notify();  
  12.     }  
  13.     // 傳送資訊  
  14.     public void send(String message) {  
  15.         // 同步佇列,加入傳送資訊  
  16.         synchronized (messages) {  
  17.             messages.add(message);  
  18.             log("Message added #messages=" + messages.size());  
  19.             // 喚醒  
  20.             messages.notify();  
  21.         }  
  22.     }  
  23.     public void run() {  
  24.         // 執行緒啟動  
  25.         log("start");  
  26.         while (running) {  
  27.             if (messages.size() == 0) {  
  28.                 try {  
  29.                     synchronized (messages) {  
  30.                         log("MessageSender wait[空閒狀態,執行緒等待]");  
  31.                         // 釋放鎖  
  32.                         messages.wait();  
  33.                     }  
  34.                 } catch (InterruptedException e) {  
  35.                     e.printStackTrace();  
  36.                     // Ignore  
  37.                 }  
  38.             }  
  39.             String[] pendingMessages = null;  
  40.             synchronized (messages) {  
  41.                 // 匯出傳送的資訊至陣列  
  42.                 pendingMessages = messages.toArray(new String[0]);  
  43.                 // 清空資訊佇列  
  44.                 messages.clear();  
  45.             }  
  46.             try {  
  47.                 if (connection == null) {  
  48.                     try {  
  49.                         synchronized (this) {  
  50.                             // 等待注入HTTP RESPONSE  
  51.                             wait();  
  52.                         }  
  53.                     } catch (InterruptedException e) {  
  54.                         // Ignore  
  55.                         e.printStackTrace();  
  56.                     }  
  57.                 }  
  58.                 // 輸出流操作  
  59.                 OutputStream out = connection.getOutputStream();  
  60.                 for (int j = 0; j < pendingMessages.length; j++) {  
  61.                     final String forecast = pendingMessages[j] + "<br>";  
  62.                     out.write(forecast.getBytes());  
  63.                     out.flush();  
  64.                     connection.flushBuffer();  
  65.                     log("Writing[寫入]:" + forecast);  
  66.                 }  
  67.             } catch (IOException e) {  
  68.                 log("IOExeption sending message", e);  
  69.             }  
  70.         }  
  71.     }  
  72.     // 停止  
  73.     public void stop() {  
  74.         running = false;  
  75.     }  
  76.     // 日誌  
  77.     private void log(Object obj) {  
  78.         System.out.println(obj);  
  79.     }  
  80.     // 日誌  
  81.     private void log(Object obj, Throwable e) {  
  82.         System.out.println(obj);  
  83.         e.printStackTrace();  
  84.     }  
  85. }  
  86. </span>  

 YAHOO天氣預報:

Java程式碼  收藏程式碼
  1. <span style="font-family: tahoma,arial,helvetica,sans-serif; font-size: small;">public class Weatherman implements Runnable {  
  2.     // 連結列表  
  3.     private final List<URL> zipCodes;  
  4.     // YAHOO WEATHER  
  5.     private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p=";  
  6.     // 傳送器  
  7.     private MessageSender messageSender;  
  8.     public Weatherman(MessageSender messageSender, Integer... zips) {  
  9.         this.messageSender = messageSender;  
  10.         zipCodes = new ArrayList<URL>(zips.length);  
  11.         for (Integer zip : zips) {  
  12.             try {  
  13.                 // 新增具體連結  
  14.                 zipCodes.add(new URL(YAHOO_WEATHER + zip));  
  15.             } catch (Exception e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.     }  
  20.     public void run() {  
  21.         System.out.println("Weatherman run[天氣預報員啟動]");  
  22.         int i = 0;  
  23.         while (i >= 0) {  
  24.             int j = i % zipCodes.size();  
  25.             SyndFeedInput input = new SyndFeedInput();  
  26.             try {  
  27.                 SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(  
  28.                         j).openStream()));  
  29.                 SyndEntry entry = (SyndEntry) feed.getEntries().get(0);  
  30.                 // 傳送資料  
  31.                 messageSender.send(entryToHtml(entry));  
  32.                 // 執行緒休眠  
  33.                 Thread.sleep(10000L);  
  34.             } catch (Exception e) {  
  35.                 e.printStackTrace();  
  36.             }  
  37.             i++;  
  38.         }  
  39.     }  
  40.     // 格式轉換  
  41.     private String entryToHtml(SyndEntry entry) {  
  42.         StringBuilder html = new StringBuilder("<h2>");  
  43.         html.append(entry.getTitle());  
  44.         html.append("</h2>");  
  45.         html.append(entry.getDescription().getValue());  
  46.         return html.toString();  
  47.     }  
  48. }</span>  

 WEB.XML配置:

Xml程式碼  收藏程式碼
  1. <span style="font-family: tahoma,arial,helvetica,sans-serif; font-size: small;">    <servlet>  
  2.         <description>TestComet</description>  
  3.         <display-name>TestComet</display-name>  
  4.         <servlet-name>TestComet</servlet-name>  
  5.         <servlet-class>cn.test.TestComet</servlet-class>  
  6.     </servlet>  
  7.     <servlet-mapping>  
  8.         <servlet-name>TestComet</servlet-name>  
  9.         <url-pattern>/TestComet</url-pattern>  
  10.     </servlet-mapping></span>  

 TOMCAT配置,NIO

Xml程式碼  收藏程式碼
  1. <span style="font-family: tahoma,arial,helvetica,sans-serif; font-size: small;"><Connector connectionTimeout="20000" port="8888" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/></span>  

參考文件:

Comet :基於 HTTP 長連線的 “ 伺服器推 ” 技術

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

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

注:

以上例子只支援FIREFOX,不支援IE

可能會出現一些問題,例如有些包可能有衝突,需要在context.xml中新增

<Loader delegate="true" />