Comet支持
Comet支持允許一個servlet異步處理IO,當數(shù)據(jù)在連接上可讀的時候(而不是使用阻塞讀)和往連接異步(最有可能的是來自一些其它原引發(fā)的事件)寫回數(shù)據(jù)的時候接收事件。
CometEvent
實現(xiàn)了org.apache.catalina.CometProcessor接口的Sevlets有他們的事件激活方法而不是使用平常的服務(wù)方法,依照誰發(fā)生的事件。事件對象引發(fā)存取通常的request和respose對象,它可能使用平常的方式。重要的區(qū)別是這些對象在任何時候保持有效和全功能,在事件BEGIN開始到END或者ERROR事件結(jié)束。下面是這些事件類型:
◆ EventType.BEGIN: 在連接處理開始將被調(diào)用。它能被用來初始化任何在request和response對象中使用的相關(guān)字段。 在這個事件處理之后和結(jié)束處理開始或者錯誤事件之間,是可以使用response對象來在連接上進行異步寫。注意,reponse對象和依靠的 OutputStream和Writer仍然不是同步的,因此當多個線程存取的時候,同步是強制的。在初始事件處理之后,request才會提交。
◆ EventType.READ: 這個指示輸入數(shù)據(jù)可以用了,可以無阻塞的讀了。可用的和InputStream或者Read的讀方法可以用來決定是否有阻塞風險: servlet會讀報告可用的數(shù)據(jù),還進一步讀取可用的數(shù)據(jù)。當遇到讀錯誤的時候,servlet通過拋出異常來報告它。拋出異常會導致一個錯誤事件被激活,連接會被關(guān)閉。另外,可以捕捉異常,清理sevlet用到的數(shù)據(jù)結(jié)構(gòu),然后調(diào)用事件的close方法。嘗試讀取這個方法執(zhí)行的request對象的數(shù)據(jù)是不允許的。
在一些平臺上,例如Windows,一個客戶端斷開會有一個READ事件。從流讀數(shù)據(jù)可能返回-1, IOException 或者 EOFException。確認你處理了所有這三種情況。如果你沒有處理,Tomcat會立刻拋出你改捕捉事件,這個時候你會被通知錯誤。
◆ EventType.END:request 結(jié)束時調(diào)用。在開始方法中初始化的字段會被重置。在這個事件處理之后,request和response對象, 還有哪些依賴的對象,會被重新使用,用來處理其它的請求。 End在 數(shù)據(jù)可用和讀文件結(jié)束的時候也會被調(diào)用。
◆ EventType.ERROR: 當容器在連接上遇到IO錯誤或者類似不可恢復的錯誤時,這個錯誤出現(xiàn)。在begin方法初始化的數(shù)據(jù)會被重置。這個事件處理之后,request和 response對象,還有一些依賴的對象,會被重新被其它請求使用。
這些事事件子類型,允許更詳細的事件處理 (注意: 一些事件需要使用org.apache.catalina.valves.CometConnectionManagerValve 值):
◆ EventSubType.TIMEOUT: 連接超時 (ERROR子類型);注意這個錯誤類型不是致命的,連接不會被關(guān)閉除非servlet使用了事件的close方法.
◆ EventSubType.CLIENT_DISCONNECT: 客戶端連接被關(guān)閉(ERROR子類型).事件的方法.
◆ EventSubType.IOEXCEPTION: 一個IO異常發(fā)生, 例如無效內(nèi)容,一個無效的大塊 (ERROR子類型).
◆ EventSubType.WEBAPP_RELOAD: web程序正在被重新加載T(END子類型).
◆ EventSubType.SESSION_END: sevlet結(jié)束會話 (END子類型).
就像上面描述的,典型的Comet請求生命周期將會包含一系列的事件:BEGIN -> READ -> READ -> READ -> ERROR/TIMEOUT. 在任何時候,servlet可以使用事件對象的close方法關(guān)閉請求的處理。
CometFilter
類似普通的過濾器,一個過濾器鏈會被激活當comet事件處理的時候。這些過濾器要應(yīng)用CometFilter接口(他和平常的過濾器接口一樣), 要被聲明和影射在部署描述符中,和常規(guī)過濾器相同的方式。當處理一個事件的時候,過濾器鏈僅僅包括哪些匹配所使用影射規(guī)則的過濾器,也要實現(xiàn) CometFiler接口.
例子代碼
下面的偽代碼servlet,實現(xiàn)了異步聊天功能,使用上面描述的API:
public class ChatServletextends 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 IOExceptionif (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 connectionsfor (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 timeouts
如果你正在使用NIO連接器,你可以設(shè)置不同的延時為不同的comet連接。設(shè)置一個延時,像下面簡單的設(shè)置一個屬性就可以:
CometEvent event.... event.setTimeout(30*1000);
或者
event.getHttpServletRequest().setAttribute("org.apache.tomcat.comet.timeout", new Integer(30 * 1000));
這里設(shè)置延時30秒. 重要提示, 為了設(shè)置這個延時, 必須在BEGIN 事件之前設(shè)置, 默認值是soTimeout。 如果你正在使用APR連接器, 所有的Comet連接有相同的延時值,是soTimeout*50。
異步寫
當 APR或者NIO啟用的時候,Tomcat支持發(fā)送大的靜態(tài)文件。這些寫操作,系統(tǒng)負載一增加,就在更高效的方式上被異步執(zhí)行。而不是使用阻塞模式發(fā)送大的response,寫內(nèi)容到一個靜態(tài)文件時可以能的,寫它用一個sendfile代碼。一個緩存值可以用來緩存response數(shù)據(jù)到一個文件而不是內(nèi)存。 Sendfile支持是可用的,如果request屬性org.apache.tomcat.sendfile.support被設(shè)置成 Boolean.TRUE.
任何一個Servlet都可以指示Tomcat執(zhí)行一個sendfile通過設(shè)置相應(yīng)的response屬性。當使用Sendfile的時候,最好確認request和response都沒被包裝,因為response主體將被connector隨后發(fā)送,他不能被過濾。除了設(shè)置3個需要的response屬性,servlet不發(fā)送任何response數(shù)據(jù),但是它可以用來修改response頭,例如設(shè)置 cookies。
◆ org.apache.tomcat.sendfile.filename: Canonical文件名, String類型
◆ org.apache.tomcat.sendfile.start: Start 偏移量,Long類型
◆ org.apache.tomcat.sendfile.start: End 偏移量,Long類型