Comet事件分析

簡介【  Introduction 】 

使用APR或者NIO API做爲鏈接器的基礎,Tomcat可以提供一些在阻塞IO之上的有效擴展,用於支持Servlet API。 
【 With usage of APR or NIO APIs as the basis of its connectors, Tomcat is able to provide a number of extensions over the regular blocking IO as provided with support for the Servlet API. 】 
重要說明:這些特性的使用須要用到APR或NIO HTTP鏈接器。java.io HTTP connector 和 AJP鏈接器是不支持這些特性的。 
【 IMPORTANT NOTE: Usage of these features requires using the APR or NIO HTTP connectors. The classic java.io HTTP connector and the AJP connectors do not support them.  】 

Comet支持【  Comet support  】 

Comet的支持容許servlet進程執行異步IO操做,當數據讀取時接收事件(而不是總使用阻塞讀取),而且向鏈接異步的寫數據(看起來更像從其餘源接收一些事件)。 
【 Comet support allows a servlet to process IO asynchronously, receiving events when data is available for reading on the connection (rather than always using a blocking read), and writing data back on connections asynchronously (most likely responding to some event raised from some other source. 】 

Comet事件【  CometEvent  】 

從org.apache.catalina.CometProcessor 接口實現的Servlet根據事件的發生具備本身的方法調用,而不使用那些通用的service方法。一般狀況下,事件對象可以訪問通用的request和response對象。主要的區別在於這些對象的有效期是從BEGIN事件直到END或者ERROR事件。事件類型以下: 
【 Servlets which implement the org.apache.catalina.CometProcessor interface will have their event method invoked rather than the usual service method, according to the event which occurred. The event object gives access to the usual request and response objects, which may be used in the usual way. The main difference is that those objects remain valid and fully functional at any time between processing of the BEGIN event until processing an END or ERROR event. The following event types exist: 】 EventType.BEGIN:在鏈接開始時候被調用。常被用來初始化request和response對象的字段。在這個事件結束後,直到END或者ERROR事件開始前,能夠用response對象向鏈接寫入數據。  注意  ,這個response對象以及所依賴的OutputStream、Writer對象並非線程安全的,因此,在多線程訪問的狀況下,須要強制完成同步工做。在初始化事件結束後,request被慎重的提交。 
【 EventType.BEGIN: will be called at the beginning of the processing of the connection. It can be used to initialize any relevant fields using the request and response objects. Between the end of the processing of this event, and the beginning of the processing of the end or error events, it is possible to use the response object to write data on the open connection. Note that the response object and dependent OutputStream and Writer are still not synchronized, so when they are accessed by multiple threads, synchronization is mandatory. After processing the initial event, the request is considered to be committed. 】 EventType.READ:這個類型標識數據能夠被有效的讀取,而且讀取過程不會被阻斷。若是這裏存在一個阻塞風險,應該使用InputStream或Reader的available和ready去判斷:servlet在確認讀取有效時去讀數據,併產生附加讀取去讀取數據有效報告。當產生讀取錯誤時,servlet能夠經過傳遞一個exception屬性去報告這個錯誤。拋出一個異常,會產生一個Error事件,而且鏈接會關閉。逐個的,可以捕獲任何錯誤,在任何數據中使用servlet和事件的close方法,執行清除動做。不容許從servlet對象執行方法外部去讀取數據。 
【 EventType.READ: This indicates that input data is available, and that one read can be made without blocking. The available and ready methods of the InputStream or Reader may be used to determine if there is a risk of blocking: the servlet should read while data is reported available, and can make one additional read should read while data is reported available. When encountering a read error, the servlet should report it by propagating the exception properly. Throwing an exception will cause the error event to be invoked, and the connection will be closed. Alternately, it is also possible to catch any exception, perform clean up on any data structure the servlet may be using, and using the close method of the event. It is not allowed to attempt reading data from the request object outside of the execution of this method. 】 
在某些平臺上,好比windows,客戶端的斷開經過一個READ事件表示。從流中讀取數據的結果會返回-一、一個IO異常或者一個EOF異常。確實的處理這三種狀況,若是你沒有捕獲IO異常,Tomcat會調用事件隊列生成一個ERROR存儲這些錯誤給你,而且你會立刻收到這個消息。 
【 On some platforms, like Windows, a client disconnect is indicated by a READ event. Reading from the stream may result in -1, an IOException or an EOFException. Make sure you properly handle all these three cases. If you don't catch the IOException, Tomcat will instantly invoke your event chain with an ERROR as it catches the error for you, and you will be notified of the error at that time. 】 EventType.END:End在請求結束時被調用,在開始時候被初始化的字段這時恢復。在這個事件調用完成後,request、response以及所依賴的對象都將被收回給其餘request使用。在request輸入時,End老是在數據有效而且到達文件末尾的時候被調用(一般標識客戶端使用管道操做提交一個請求)。 
【 EventType.END: End may be called to end the processing of the request. Fields that have been initialized in the begin method should be reset. After this event has been processed, the request and response objects, as well as all their dependent objects will be recycled and used to process other requests. End will also be called when data is available and the end of file is reached on the request input (this usually indicates the client has pipelined a request). 】 EventType.ERROR:Error當鏈接時發生IO異常或者相似的不可恢復的錯誤時被容器調用。在開始時候被初始化的字段在這時候被恢復。在這個事件調用完成後,request、response以及所依賴的對象都將被收回給其餘request使用。 
【 EventType.ERROR: Error will be called by the container in the case where an IO exception or a similar unrecoverable error occurs on the connection. Fields that have been initialized in the begin method should be reset. After this event has been processed, the request and response objects, as well as all their dependent objects will be recycled and used to process other requests. 】 
還有一些自類型用以更細緻的控制進程(注意:有些子類型須要 org.apache.catalina.valves.CometConnectionManagerValve 的值是有效的) 
【 There are some event subtypes which allow finer processing of events (note: some of these events require usage of the org.apache.catalina.valves.CometConnectionManagerValve valve): 】 EventSubType.TIMEOUT:鏈接超時(ERROR的子類型);注意這個ERROR類型不是致命的,鏈接不會關閉,除非servlet使用事件的close方法。 
【EventSubType.TIMEOUT: The connection timed out (sub type of ERROR); note that this ERROR type is not fatal, and the connection will not be closed unless the servlet uses the close method of the event. 】 EventSubType.CLIENT_DISCONNECT:客戶端鏈接關閉(ERROR的子類型)。事件的方法 
【 EventSubType.CLIENT_DISCONNECT: The client connection was closed (sub type of ERROR). method of the event. 】 EventSubType.IOEXCEPTION:一個IO異常發生,好比上下文失效,例如,一個阻塞失效(ERROR的子類型)。 
【 EventSubType.IOEXCEPTION: An IO exception occurred, such as invalid content, for example, an invalid chunk block (sub type of ERROR). 】 EventSubType.WEBAPP_RELOAD:web應用重載(END的子類型)。 
【 EventSubType.WEBAPP_RELOAD: The web application is being reloaded (sub type of END). 】 EventSubType.SESSION_END:Session結束(END的子類型)。 
【 EventSubType.SESSION_END: The servlet ended the session (sub type of END). 】 
如上所述,一個Comet的典型聲明週期經歷這麼一個過程:BEGIN->READ->READ->READ->ERROR/TIMEOUT。任什麼時候候,servlet經過事件對象的close方法結束一個請求。 
As described above, the typical lifecycle of a Comet request will consist in a series of events such as: BEGIN -> READ -> READ -> READ -> ERROR/TIMEOUT. At any time, the servlet may end processing of the request by using the close method of the event object. 

Comet過濾器【  CometFilter  】 

與規則過濾器相似,在Comet時間被處理時,一個過濾器隊列被調用。這些過濾器實現了CometFilter接口(和規則過濾器接口一樣的方式工做),並與規則過濾器使用一樣的方法在部署描述中聲明。事件處理時的過濾器隊列只包含那些標記了一般映射規則的過濾器和實現了CometFilter接口的。 
【 Similar to regular filters, a filter chain is invoked when comet events are processed. These filters should implement the CometFilter interface (which works in the same way as the regular Filter interface), and should be declared and mapped in the deployment descriptor in the same way as a regular filter. The filter chain when processing an event will only include filters which match all the usual mapping rules, and also implement the CometFiler interface. 】 

<!--()--> 範例代碼【  Example code  】 

下面的Servlet僞代碼實現了異步聊天室的功能: 
【 The following pseudo code servlet implements asynchronous chat functionality using the API described above: 】 

     
public class ChatServlet     extends HttpServlet implements CometProcessor {      protected ArrayList<HttpServletResponse> connections =          new ArrayList<HttpServletResponse>();     protected MessageSender messageSender = null;          public void init() throws ServletException {         messageSender = new MessageSender();         Thread messageSenderThread =              new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");         messageSenderThread.setDaemon(true);         messageSenderThread.start();     }      public void destroy() {         connections.clear();         messageSender.stop();         messageSender = null;     }      /**      * Process the given Comet event.      *       * @param event The Comet event that will be processed      * @throws IOException      * @throws ServletException      */     public void event(CometEvent event)         throws IOException, ServletException {         HttpServletRequest request = event.getHttpServletRequest();         HttpServletResponse response = event.getHttpServletResponse();         if (event.getEventType() == CometEvent.EventType.BEGIN) {             log("Begin for session: " + request.getSession(true).getId());             PrintWriter writer = response.getWriter();             writer.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");             writer.println("<head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");             writer.flush();             synchronized(connections) {                 connections.add(response);             }         } else if (event.getEventType() == CometEvent.EventType.ERROR) {             log("Error for session: " + request.getSession(true).getId());             synchronized(connections) {                 connections.remove(response);             }             event.close();         } else if (event.getEventType() == CometEvent.EventType.END) {             log("End for session: " + request.getSession(true).getId());             synchronized(connections) {                 connections.remove(response);             }             PrintWriter writer = response.getWriter();             writer.println("</body></html>");             event.close();         } else if (event.getEventType() == CometEvent.EventType.READ) {             InputStream is = request.getInputStream();             byte[] buf = new byte[512];             do {                 int n = is.read(buf); //can throw an IOException                 if (n > 0) {                     log("Read " + n + " bytes: " + new String(buf, 0, n)                              + " for session: " + request.getSession(true).getId());                 } else if (n < 0) {                     error(event, request, response);                     return;                 }             } while (is.available() > 0);         }     }      public class MessageSender implements Runnable {          protected boolean running = true;         protected ArrayList<String> messages = new ArrayList<String>();                  public MessageSender() {         }                  public void stop() {             running = false;         }          /**          * Add message for sending.          */         public void send(String user, String message) {             synchronized (messages) {                 messages.add("[" + user + "]: " + message);                 messages.notify();             }         }          public void run() {              while (running) {                  if (messages.size() == 0) {                     try {                         synchronized (messages) {                             messages.wait();                         }                     } catch (InterruptedException e) {                         // Ignore                     }                 }                  synchronized (connections) {                     String[] pendingMessages = null;                     synchronized (messages) {                         pendingMessages = messages.toArray(new String[0]);                         messages.clear();                     }                     // Send any pending message on all the open connections                     for (int i = 0; i < connections.size(); i++) {                         try {                             PrintWriter writer = connections.get(i).getWriter();                             for (int j = 0; j < pendingMessages.length; j++) {                                 writer.println(pendingMessages[j] + "<br>");                             }                             writer.flush();                         } catch (IOException e) {                             log("IOExeption sending message", e);                         }                     }                 }              }          }      }  }   
 
     

<!--()--> Comet超時【  Comet timeouts  】 

若是使用NIO鏈接器,你可以爲每一個comet鏈接器設置獨立的超時時間。設置超時時間只須要向下面代碼這樣設置一個request屬性: 
【 If you are using the NIO connector, you can set individual timeouts for your different comet connections. To set a timeout, simply set a request attribute like the following code shows: 】 

CometEvent event.... event.setTimeout(30*1000);
or 
event.getHttpServletRequest().setAttribute("org.apache.tomcat.comet.timeout", new Integer(30 * 1000));
 這裏設置了30秒的超時時間。切記,爲了設置超時,必須完成BEGIN事件。默認值是 soTimeout 
【 This sets the timeout to 30 seconds. Important note, in order to set this timeout, it has to be done on the BEGIN event. The default value is soTimeout 】 
若是你使用APR鏈接器,全部的Comet鏈接都具備一樣的超時時間。 soTimeout*50 
If you are using the APR connector, all Comet connections will have the same timeout value. It is soTimeout*50 

<!--()--> 異步寫操做【  Asynchronous writes  】 

當APR或者NIO可用時,Tomcat支持使用sendfile方式去發送一個大文件。這種增長系統負荷的寫操做異步執行是很是有效的方法。做爲發送一個大塊的阻塞寫操做的替代方式,用一個sendfile代碼去異步寫一個文件的內容是不錯的方式。爲response數據用文件做爲緩衝比緩衝到內存中要好。當request的org.apache.tomcat.sendfile.support設置爲Boolean.TRUE時,Sendfile支持是有效的。 
【 When APR or NIO is enabled, Tomcat supports using sendfile to send large static files. These writes, as soon as the system load increases, will be performed asynchronously in the most efficient way. Instead of sending a large response using blocking writes, it is possible to write content to a static file, and write it using a sendfile code. A caching valve could take advantage of this to cache the response data in a file rather than store it in memory. Sendfile support is available if the request attribute org.apache.tomcat.sendfile.support is set to Boolean.TRUE . 】 
經過設置恰當的請求屬性,servlet可以調用Tomcat去執行一個sendfile過程。這須要爲響應正確的設置長度。使用sendfile最好的作法是要確保request和response被包裝起來了。由於做爲響應的內容會比鏈接器傳輸要晚一些,它不能被過濾。出了設置3個必要的request屬性,selvlet不去發送任何響應數據,但有不少種方法去修改響應的頭(如設置cookie) 
【 Any servlet can instruct Tomcat to perform a sendfile call by setting the appropriate request attributes. It is also necessary to correctly set the content length for the response. When using sendfile, it is best to ensure that neither the request or response have been wrapped, since as the response body will be sent later by the connector itself, it cannot be filtered. Other than setting the 3 needed request attributes, the servlet should not send any response data, but it may use any method which will result in modifying the response header (like setting cookies). 】 org.apache.tomcat.sendfile.filename:一個字符串來描述一個文件名稱。 
【 org.apache.tomcat.sendfile.filename: Canonical filename of the file which will be sent as a String 】 org.apache.tomcat.sendfile.start:開始位置的偏移量,一個Long類型 
【 org.apache.tomcat.sendfile.start: Start offset as a Long 】 org.apache.tomcat.sendfile.end: 結束位置的偏移量,一個Long類型 
【 org.apache.tomcat.sendfile.end: End offset as a Long 】 
----------------------------------------------------------------------------------- 

人太笨了,這篇文章搞了我好幾個小時,暈死,這要翻譯完了不要把我累死呀。但願之後水平好了之後會快一些吧。javascript

 

 轉載自:http://www.boyunjian.com/do/article/snapshot.do?uid=com.iteye.icefox-wjx%2Fblog%2F900685html

相關文章
相關標籤/搜索