利用Tomcat7.0新特性,用BS实现CS的聊天服务器

Tomcat7.0.0已经出来了,关注它已经支持servlet3.0规范,servlet3.0规范有一个很值得期待的特性就是,支持异步IO通信,何为异步响应,就是保持长连接,让servlet实现原先的TCP Server才能做到的事,就像我以前写的一个WEB IM。没有用到comet,使用Ajax轮询聊天,反应慢不说,很多时候轮询的资源是被浪费掉的,杯具啊

      现在好了Tomcat7.0.0已经原生支持comet和异步IO,但是需要 APR 或者NIO HTTP连接器 应该在新的servlet-api,会提供tomcat7.0-guide的原文如下:

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, 
实际是使用,需要增加对NIO的支持,要做的仅仅是在server.xml里边修改connector:

<connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="8080" redirectport="8443" connectiontimeout="20000">


    要用好这个comet需要了解comet的几个事件


EventType.BEGIN:开始连接,比如用户的一个聊天消息刚发送到了你的comet聊天服务器,建立连接后,但是你还没有开始读。(因为被加锁了,有可能正在读其他用户的聊天消息)你可以通过CometEvent 对象获取该用户request,response,当锁被你获得后,使用这些request该怎么做就由你了,比如得到这个request的聊天正文或者头域。最重要的是,你可以取得这个用户的response,放到你定义的一个全局容器中,比如这样
Java代码 复制代码
  1. protected ArrayList connections    
  2.         new ArrayList();      
  3. synchronized(connections)    
  4.                 connections.add(response);    
  5.               
protected ArrayList connections = new ArrayList(); synchronized(connections) { connections.add(response); }


 

这端代码很重要,因为只有保存了用户响应的句柄,才是异步comet的关键所在,你可以决定什么时候,什么内容什么顺序把消息分发给哪一个用户.

  • EventType.READ:这表明你可以读用户的消息,并且消息是有效地,并且告诉你这时候读该用户消息是不会有阻塞的风险,当你不幸遇到了读取错误,将会抛出一个异常,这时候会转到下面的EventType.ERROR ,然后这个连接将被关闭,但是你也可以catch这个异常。在window中,一个客户端断开可能是由于一个read事件,读取流可能导致返回-1,IOException,或者 EOFException.确保你处理了以上三种情况,否则你就会跳到EventType.ERROR事件中。
  • EventType.END: 一次用户请求结束,也就是一次request,服务器已经发回了response,rquest,response没有被回收,但是假如客户端浏览器断开这个comet.这样我们前面那个response的容器就需要清除该response,

 

Java代码 复制代码
  1. synchronized(connections)    
  2.                 connections.remove(response);    
  3.               
synchronized(connections) { connections.remove(response); }

 

  • EventType.ERROR:当一次IO异常或者一次不可发生的错误发生,那些在begin方法中初始化的资源会被重置,这个request和response将会被回收(就是某一个发生ioexception客户的资源都要被重置,抓住这个错误可以提示给用户一些有用的信息,比如:你和服务器的连接发生了错误,请重新登录聊天室)
     


下面这个是模拟TCP CHART Server的Servlet聊天服务器,是基于异步长连接的

Java代码 复制代码
  1. public class ChatServlet    
  2.     extends HttpServlet implements CometProcessor    
  3.   
  4.     protected ArrayList connections    
  5.         new ArrayList();    
  6.     protected MessageSender messageSender null   
  7.        
  8.     public void init() throws ServletException    
  9. //聊天服务器servlet启动的时候自动启动一个线程来接收用户的聊天消息,并广播出去    
  10.         messageSender new MessageSender();    
  11.         Thread messageSenderThread    
  12.             new Thread(messageSender, &quot;MessageSender[&quot; getServletContext().getContextPath() &quot;]&quot;);    
  13.         messageSenderThread.setDaemon(true);    
  14.         messageSenderThread.start();    
  15.        
  16.   
  17.     public void destroy()    
  18. //清除资源    
  19.         connections.clear();    
  20.         messageSender.stop();    
  21.         messageSender null   
  22.        
  23.   
  24.         
  25.     public void event(CometEvent event)    
  26.         throws IOException, ServletException    
  27.         HttpServletRequest request event.getHttpServletRequest();    
  28.         HttpServletResponse response event.getHttpServletResponse();    
  29.         if (event.getEventType() == CometEvent.EventType.BEGIN)    
  30. //加入刚收到这个用户的请求,触发CometEvent.EventType.BEGIN事件,先打印出一些消息头,并且把这个用户的//response保存在缓存容器中,以备广播用    
  31.             log(&quot;Begin for session: &quot; request.getSession(true).getId());    
  32.             PrintWriter writer response.getWriter();    
  33.             writer.println(&quot;&quot;-//w3c//dtd html 4.0 transitional//en/&quot;&gt;&quot;);    
  34.             writer.println(&quot;JSP Chat&quot;);    
  35.             writer.flush();    
  36.             synchronized(connections)    
  37.                 connections.add(response);    
  38.                
  39.            
  40. 假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时    
  41. else if (event.getEventType() == CometEvent.EventType.ERROR)    
  42.             log(&quot;Error for session: &quot; request.getSession(true).getId());    
  43.             synchronized(connections)    
  44.                 connections.remove(response);    
  45.                
  46.             event.close();    
  47.            
  48. //结束时候也是释放连接,清空这个缓存的response句柄,值得注意的是,这时候用户已经断开聊天服务器    
  49.   
  50. else if (event.getEventType() == CometEvent.EventType.END) {   
  51.             log("End for session: " request.getSession(true).getId());   
  52.             synchronized(<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s) {   
  53.                 <SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s.remove(response);   
  54.             }   
  55.             PrintWriter writer response.getWriter();   
  56.             writer.println("</body></html>");   
  57.             event.close();   
  58.         else if (event.getEventType() == CometEvent.EventType.READ) {   
  59.             InputStream is request.getInputStream();   
  60.             byte[] buf new byte[512];   
  61.             do {   
  62.                 int is.read(buf); //can throw an IOException//由于已经在线程中已经读消息和广播消息了,这里这个事件主要用来log输出用户发来的聊天内容   
  63.                 if (n 0{   
  64.                     log("Read " bytes: " new String(buf, 0n)    
  65.                             for session: " request.getSession(true).getId());   
  66.                 else if (n 0{   
  67.                     error(event, request, response);   
  68.                     return;   
  69.                 }   
  70.             while (is.available() 0);   
  71.         }   
  72.     }   
  73.   
  74.    
public class ChatServlet extends HttpServlet implements CometProcessor { protected ArrayList connections = new ArrayList(); protected MessageSender messageSender = null; public void init() throws ServletException { //聊天服务器servlet启动的时候自动启动一个线程来接收用户的聊天消息,并广播出去 messageSender = new MessageSender(); Thread messageSenderThread = new Thread(messageSender, &quot;MessageSender[&quot; + getServletContext().getContextPath() + &quot;]&quot;); messageSenderThread.setDaemon(true); messageSenderThread.start(); } public void destroy() { //清除资源 connections.clear(); messageSender.stop(); messageSender = null; } public void event(CometEvent event) throws IOException, ServletException { HttpServletRequest request = event.getHttpServletRequest(); HttpServletResponse response = event.getHttpServletResponse(); if (event.getEventType() == CometEvent.EventType.BEGIN) { //加入刚收到这个用户的请求,触发CometEvent.EventType.BEGIN事件,先打印出一些消息头,并且把这个用户的//response保存在缓存容器中,以备广播用 log(&quot;Begin for session: &quot; + request.getSession(true).getId()); PrintWriter writer = response.getWriter(); writer.println(&quot;&quot;-//w3c//dtd html 4.0 transitional//en/&quot;&gt;&quot;); writer.println(&quot;JSP Chat&quot;); writer.flush(); synchronized(connections) { connections.add(response); } } 假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时 else if (event.getEventType() == CometEvent.EventType.ERROR) { log(&quot;Error for session: &quot; + request.getSession(true).getId()); synchronized(connections) { connections.remove(response); } event.close(); } //结束时候也是释放连接,清空这个缓存的response句柄,值得注意的是,这时候用户已经断开聊天服务器 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//由于已经在线程中已经读消息和广播消息了,这里这个事件主要用来log输出用户发来的聊天内容 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); } }

 

Java代码 复制代码
  1. //该线程用来接收用户的消息,和向所有用户广播消息,应该不是很难   
  2.   
  3.     public class MessageSender implements Runnable {   
  4.   
  5.         protected boolean running true;   
  6.         protected ArrayList<String> messages new ArrayList<String>();   
  7.            
  8.         public MessageSender() {   
  9.         }   
  10.            
  11.         public void stop() {   
  12.             running false;   
  13.         }   
  14.   
  15.           
  16.         public void send(String user, String message) {   
  17.             synchronized (messages) {   
  18.                 messages.add("[" user "]: " message);   
  19.                 messages.notify();   
  20.             }   
  21.         }   
  22.   
  23.         public void run() {   
  24.   
  25.             while (running) {   
  26.   
  27.                 if (messages.size() == 0{   
  28.                     try {   
  29.                         synchronized (messages) {   
  30.                             messages.wait();   
  31.                         }   
  32.                     catch (InterruptedException e) {   
  33.                         // Ignore   
  34.                     }   
  35.                 }   
  36.   
  37.                 synchronized (connections) {   
  38.                     String[] pendingMessages null;   
  39.                     synchronized (messages) {   
  40.                         pendingMessages messages.toArray(new String[0]);   
  41.                         messages.clear();   
  42.                     }   
  43.                     // 传说中的广播   
  44.                     for (int 0connections.size(); i++) {   
  45.                         try {   
  46.                             PrintWriter writer connections.get(i).getWriter();   
  47.                             for (int 0pendingMessages.length; j++) {   
  48.                                 writer.println(pendingMessages[j] "<br>");   
  49.                             }   
  50.                             writer.flush();   
  51.                         catch (IOException e) {   
  52.                             log("IOExeption sending message"e);   
  53.                         }   
  54.                     }   
  55.                 }   
  56.   
  57.             }   
  58.   
  59.         }   
  60.   
  61.     }   
  62.   
  63. }   

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值