利用Tomcat7.0新特性,用BS实现CS的聊天服务器
2011-04-21 14:33
405 查看
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代码
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代码
synchronized(connections) {
connections.remove(response);
}
EventType.ERROR:当一次IO异常或者一次不可发生的错误发生,那些在begin方法中初始化的资源会被重置,这个request和response将会被回收(就是某一个发生ioexception客户的资源都要被重置,抓住这个错误可以提示给用户一些有用的信息,比如:你和服务器的连接发生了错误,请重新登录聊天室)
下面这个是模拟TCP CHART Server的Servlet聊天服务器,是基于异步长连接的
Java代码
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, "MessageSender[" + getServletContext().getContextPath() + "]");
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("Begin for session: " + request.getSession(true).getId());
PrintWriter writer = response.getWriter();
writer.println(""-//w3c//dtd html 4.0 transitional//en/">");
writer.println("JSP Chat");
writer.flush();
synchronized(connections) {
connections.add(response);
}
}
假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时
else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + 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(<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s) {
<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s.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代码
//该线程用来接收用户的消息,和向所有用户广播消息,应该不是很难
public class MessageSender implements Runnable {
protected boolean running = true;
protected ArrayList<String> messages = new ArrayList<String>();
public MessageSender() {
}
public void stop() {
running = false;
}
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();
}
// 传说中的广播
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);
}
}
}
}
}
}
}
现在好了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代码
protected ArrayList connections =
new ArrayList();
synchronized(connections) {
connections.add(response);
}
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代码
synchronized(connections) {
connections.remove(response);
}
synchronized(connections) { connections.remove(response); }
EventType.ERROR:当一次IO异常或者一次不可发生的错误发生,那些在begin方法中初始化的资源会被重置,这个request和response将会被回收(就是某一个发生ioexception客户的资源都要被重置,抓住这个错误可以提示给用户一些有用的信息,比如:你和服务器的连接发生了错误,请重新登录聊天室)
下面这个是模拟TCP CHART Server的Servlet聊天服务器,是基于异步长连接的
Java代码
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, "MessageSender[" + getServletContext().getContextPath() + "]");
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("Begin for session: " + request.getSession(true).getId());
PrintWriter writer = response.getWriter();
writer.println(""-//w3c//dtd html 4.0 transitional//en/">");
writer.println("JSP Chat");
writer.flush();
synchronized(connections) {
connections.add(response);
}
}
假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时
else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + 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(<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s) {
<SPAN style="BACKGROUND-COLOR: yellow">connection</SPAN>s.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);
}
}
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, "MessageSender[" + getServletContext().getContextPath() + "]"); 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("Begin for session: " + request.getSession(true).getId()); PrintWriter writer = response.getWriter(); writer.println(""-//w3c//dtd html 4.0 transitional//en/">"); writer.println("JSP Chat"); writer.flush(); synchronized(connections) { connections.add(response); } } 假如IO错误了,当然释放这个连接,清空这个缓存的response句柄,该用户web im应该提示服务器错误,或者超时 else if (event.getEventType() == CometEvent.EventType.ERROR) { log("Error for session: " + 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代码
//该线程用来接收用户的消息,和向所有用户广播消息,应该不是很难
public class MessageSender implements Runnable {
protected boolean running = true;
protected ArrayList<String> messages = new ArrayList<String>();
public MessageSender() {
}
public void stop() {
running = false;
}
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();
}
// 传说中的广播
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);
}
}
}
}
}
}
}
相关文章推荐
- 利用Tomcat7.0新特性,用BS实现CS的聊天服务器
- 利用tomcat服务器实现多线程下载
- 利用简易Tomcat服务器结合MysqL实现Android手机注册与登录(服务器部分)
- 利用tomcat服务器实现多线程下载 2
- 利用tomcat服务器实现多线程下载 3
- 利用简易Tomcat服务器结合MysqL实现Android手机注册与登录(客户端部分)
- 利用简易Tomcat服务器结合MysqL实现Android手机注册与登录(服务器部分)
- 利用AJAX在BS下实现CS模式下的可交互的应用程序
- nodejs的cs模式聊天客户端和服务器实现
- HTML5基于Tomcat 7.0实现WebSocket连接并实现简单的实时聊天
- HTML5基于Tomcat 7.0实现WebSocket连接并实现简单的实时聊天
- Java 利用 Socket 实现服务器客户端聊天
- 利用 Socket 实现服务器客户端聊天
- TOMCAT7.0利用memcached-session-manager 实现SESSION共享
- 利用简易Tomcat服务器结合MysqL实现Android手机注册与登录(客户端部分)
- 利用boost:asio写的简单聊天服务器三
- 利用PHP的OOP特性实现数据保护(1)
- 利用javascript的面向对象的特性实现限制试用期
- 利用BIND的VIEW功能实现国内国处用户解析到不同服务器的智能解析
- 利用缓存实现APP端与服务器接口交互的Session控制