免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2747 | 回复: 0
打印 上一主题 下一主题

利用Tomcat7.0新特性,用BS实现CS的聊天服务器 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-03-01 20:13 |只看该作者 |倒序浏览
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,放到你定义的一个全局容器中,比如这样
  1. protected ArrayList connections =
  2.         new ArrayList();   
  3. synchronized(connections) {
  4.                 connections.add(response);
  5.             }
复制代码
这端代码很重要,因为只有保存了用户响应的句柄,才是异步comet的关键所在,你可以决定什么时候,什么内容什么顺序把消息分发给哪一个用户.

EventType.READ:这表明你可以读用户的消息,并且消息是有效地,并且告诉你这时候读该用户消息是不会有阻塞的风险,当你不幸遇到了读取错误,将会抛出一个异常,这时候会转到下面的EventType.ERROR ,然后这个连接将被关闭,但是你也可以catch这个异常。在window中,一个客户端断开可能是由于一个read事件,读取流可能导致返回-1,IOException,或者 EOFException.确保你处理了以上三种情况,否则你就会跳到EventType.ERROR事件中。
EventType.END: 一次用户请求结束,也就是一次request,服务器已经发回了response,rquest,response没有被回收,但是假如客户端浏览器断开这个comet.这样我们前面那个response的容器就需要清除该response,
  1. synchronized(connections) {
  2.                 connections.remove(response);
  3.             }
复制代码
EventType.ERROR:当一次IO异常或者一次不可发生的错误发生,那些在begin方法中初始化的资源会被重置,这个request和response将会被回收(就是某一个发生ioexception客户的资源都要被重置,抓住这个错误可以提示给用户一些有用的信息,比如:你和服务器的连接发生了错误,请重新登录聊天室)


下面这个是模拟TCP CHART Server的Servlet聊天服务器,是基于异步长连接的
  1. public class ChatServlet
  2.     extends HttpServlet implements CometProcessor {

  3.     protected ArrayList connections =
  4.         new ArrayList();
  5.     protected MessageSender messageSender = null;
  6.    
  7.     public void init() throws ServletException {
  8. //聊天服务器servlet启动的时候自动启动一个线程来接收用户的聊天消息,并广播出去
  9.         messageSender = new MessageSender();
  10.         Thread messageSenderThread =
  11.             new Thread(messageSender, &quot;MessageSender[&quot; + getServletContext().getContextPath() + &quot;]&quot;);
  12.         messageSenderThread.setDaemon(true);
  13.         messageSenderThread.start();
  14.     }

  15.     public void destroy() {
  16. //清除资源
  17.         connections.clear();
  18.         messageSender.stop();
  19.         messageSender = null;
  20.     }

  21.     /**
  22.      * Process the given Comet event.
  23.      *
  24.      * @param event The Comet event that will be processed
  25.      * @throws IOException
  26.      * @throws ServletException
  27.      */
  28.     public void event(CometEvent event)
  29.         throws IOException, ServletException {
  30.         HttpServletRequest request = event.getHttpServletRequest();
  31.         HttpServletResponse response = event.getHttpServletResponse();
  32.         if (event.getEventType() == CometEvent.EventType.BEGIN) {
  33. //加入刚收到这个用户的请求,触发CometEvent.EventType.BEGIN事件,先打印出一些消息头,并且把这个用户的//response保存在缓存容器中,以备广播用
  34.             log(&quot;Begin for session: &quot; + request.getSession(true).getId());
  35.             PrintWriter writer = response.getWriter();
  36.             writer.println(&quot;&quot;-//w3c//dtd html 4.0 transitional//en\&quot;&gt;&quot;);
  37.             writer.println(&quot;JSP Chat&quot;);
  38.             writer.flush();
  39.             synchronized(connections) {
  40.                 connections.add(response);
  41.             }
  42.         }
  43. 假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时
  44. else if (event.getEventType() == CometEvent.EventType.ERROR) {
  45.             log(&quot;Error for session: &quot; + request.getSession(true).getId());
  46.             synchronized(connections) {
  47.                 connections.remove(response);
  48.             }
  49.             event.close();
  50.         }
  51. //结束时候也是释放连接,清空这个缓存的response句柄,值得注意的是,这时候用户已经断开聊天服务器

  52. else if (event.getEventType() == CometEvent.EventType.END) {
  53.             log("End for session: " + request.getSession(true).getId());
  54.             synchronized(<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s) {
  55.                 <SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s.remove(response);
  56.             }
  57.             PrintWriter writer = response.getWriter();
  58.             writer.println("</body></html>");
  59.             event.close();
  60.         } else if (event.getEventType() == CometEvent.EventType.READ) {
  61.             InputStream is = request.getInputStream();
  62.             byte[] buf = new byte[512];
  63.             do {
  64.                 int n = is.read(buf); //can throw an IOException//由于已经在线程中已经读消息和广播消息了,这里这个事件主要用来log输出用户发来的聊天内容
  65.                 if (n > 0) {
  66.                     log("Read " + n + " bytes: " + new String(buf, 0, n)
  67.                             + " for session: " + request.getSession(true).getId());
  68.                 } else if (n < 0) {
  69.                     error(event, request, response);
  70.                     return;
  71.                 }
  72.             } while (is.available() > 0);
  73.         }
  74.     }

复制代码
  1. //该线程用来接收用户的消息,和向所有用户广播消息,应该不是很难

  2.     public class MessageSender implements Runnable {

  3.         protected boolean running = true;
  4.         protected ArrayList<String> messages = new ArrayList<String>();
  5.         
  6.         public MessageSender() {
  7.         }
  8.         
  9.         public void stop() {
  10.             running = false;
  11.         }

  12.         /**
  13.          * Add message for sending.
  14.          */
  15.         public void send(String user, String message) {
  16.             synchronized (messages) {
  17.                 messages.add("[" + user + "]: " + message);
  18.                 messages.notify();
  19.             }
  20.         }

  21.         public void run() {

  22.             while (running) {

  23.                 if (messages.size() == 0) {
  24.                     try {
  25.                         synchronized (messages) {
  26.                             messages.wait();
  27.                         }
  28.                     } catch (InterruptedException e) {
  29.                         // Ignore
  30.                     }
  31.                 }

  32.                 synchronized (connections) {
  33.                     String[] pendingMessages = null;
  34.                     synchronized (messages) {
  35.                         pendingMessages = messages.toArray(new String[0]);
  36.                         messages.clear();
  37.                     }
  38.                     // 传说中的广播
  39.                     for (int i = 0; i < connections.size(); i++) {
  40.                         try {
  41.                             PrintWriter writer = connections.get(i).getWriter();
  42.                             for (int j = 0; j < pendingMessages.length; j++) {
  43.                                 writer.println(pendingMessages[j] + "<br>");
  44.                             }
  45.                             writer.flush();
  46.                         } catch (IOException e) {
  47.                             log("IOExeption sending message", e);
  48.                         }
  49.                     }
  50.                 }

  51.             }

  52.         }

  53.     }

  54. }

复制代码
原文 http://www.javaeye.com/topic/704973
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP