基于Tomcat7、Java、WebSocket的服务器推送聊天室 .

xxg0 2014-09-29

前言

 
        HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要求比较高的应用。以前的服务器消息推送大部分采用的都是“轮询”和“长连接”技术,这两中技术都会对服务器产生相当大的开销,而且实时性不是特别高。WebSocket技术对只会产生很小的开销,并且实时性特别高。下面就开始讲解如何利用WebSocket技术开发聊天室。在这个实例中,采用的是Tomcat7服务器,每个服务器对于WebSocket的实现都是不一样的,所以这个实例只能在Tomcat服务器中运行,不过目前Spring已经推出了WebSocket的API,能够兼容各个服务器的实现,大家可以查阅相关的资料进行了解,在这里就不介绍了,下图是聊天室的效果图:
基于Tomcat7、Java、WebSocket的服务器推送聊天室 .
        在这里实例中,实现了消息的实时推送,还实现了聊天用户的上下线通知。下面就开始具体讲解如何实现。
 

后台处理

 
        Tomcat实现WebSocket的主要是依靠org.apache.catalina.websocket.MessageInbound这个类,这个类的在{TOMCAT_HOME}/lib/catalina.jar中,所以你开发的时候需要将catalina.jar和tomcat-coyote.jar引入进来,下面这段代码就是暴露给客户端连接地址的Servlet:
 
package com.ibcio;  
  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServletRequest;  
  
import org.apache.catalina.websocket.StreamInbound;  
  
@WebServlet(urlPatterns = { "/message"})  
//如果要接收浏览器的ws://协议的请求就必须实现WebSocketServlet这个类  
public class WebSocketMessageServlet extends org.apache.catalina.websocket.WebSocketServlet {  
  
    private static final long serialVersionUID = 1L;  
      
    public static int ONLINE_USER_COUNT = 1;  
      
    public String getUser(HttpServletRequest request){  
        return (String) request.getSession().getAttribute("user");  
    }  
  
    //跟平常Servlet不同的是,需要实现createWebSocketInbound,在这里初始化自定义的WebSocket连接对象  
    @Override  
    protected StreamInbound createWebSocketInbound(String subProtocol,HttpServletRequest request) {  
        return new WebSocketMessageInbound(this.getUser(request));  
    }  
}  
        这个Servlet跟普通的Servlet有些不同,继承的WebSocketServlet类,并且要重写createWebSocketInbound方法。这个类中Session中的user属性是用户进入index.jsp的时候设置的,记录当前用户的昵称。下面就是自己实现的WebSocket连接对象类WebSocketMessageInbound类的代码:
package com.ibcio;  
  
import java.io.IOException;  
import java.nio.ByteBuffer;  
import java.nio.CharBuffer;  
  
import net.sf.json.JSONObject;  
  
import org.apache.catalina.websocket.MessageInbound;  
import org.apache.catalina.websocket.WsOutbound;  
  
public class WebSocketMessageInbound extends MessageInbound {  
  
    //当前连接的用户名称  
    private final String user;  
  
    public WebSocketMessageInbound(String user) {  
        this.user = user;  
    }  
  
    public String getUser() {  
        return this.user;  
    }  
  
    //建立连接的触发的事件  
    @Override  
    protected void onOpen(WsOutbound outbound) {  
        // 触发连接事件,在连接池中添加连接  
        JSONObject result = new JSONObject();  
        result.element("type", "user_join");  
        result.element("user", this.user);  
        //向所有在线用户推送当前用户上线的消息  
        WebSocketMessageInboundPool.sendMessage(result.toString());  
          
        result = new JSONObject();  
        result.element("type", "get_online_user");  
        result.element("list", WebSocketMessageInboundPool.getOnlineUser());  
        //向连接池添加当前的连接对象  
        WebSocketMessageInboundPool.addMessageInbound(this);  
        //向当前连接发送当前在线用户的列表  
        WebSocketMessageInboundPool.sendMessageToUser(this.user, result.toString());  
    }  
  
    @Override  
    protected void onClose(int status) {  
        // 触发关闭事件,在连接池中移除连接  
        WebSocketMessageInboundPool.removeMessageInbound(this);  
        JSONObject result = new JSONObject();  
        result.element("type", "user_leave");  
        result.element("user", this.user);  
        //向在线用户发送当前用户退出的消息  
        WebSocketMessageInboundPool.sendMessage(result.toString());  
    }  
  
    @Override  
    protected void onBinaryMessage(ByteBuffer message) throws IOException {  
        throw new UnsupportedOperationException("Binary message not supported.");  
    }  
  
    //客户端发送消息到服务器时触发事件  
    @Override  
    protected void onTextMessage(CharBuffer message) throws IOException {  
        //向所有在线用户发送消息  
        WebSocketMessageInboundPool.sendMessage(message.toString());  
    }  
}  

     代码中的主要实现了onOpen、onClose、onTextMessage方法,分别处理用户上线、下线、发送消息。在这个类中有个WebSocketMessageInboundPool连接池类,这个类是用来管理目前在线的用户的连接,下面是这个类的代码:
package com.ibcio;  
  
import java.io.IOException;  
import java.nio.CharBuffer;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.Set;  
  
public class WebSocketMessageInboundPool {  
  
    //保存连接的MAP容器  
    private static final Map<String,WebSocketMessageInbound > connections = new HashMap<String,WebSocketMessageInbound>();  
      
    //向连接池中添加连接  
    public static void addMessageInbound(WebSocketMessageInbound inbound){  
        //添加连接  
        System.out.println("user : " + inbound.getUser() + " join..");  
        connections.put(inbound.getUser(), inbound);  
    }  
      
    //获取所有的在线用户  
    public static Set<String> getOnlineUser(){  
        return connections.keySet();  
    }  
      
    public static void removeMessageInbound(WebSocketMessageInbound inbound){  
        //移除连接  
        System.out.println("user : " + inbound.getUser() + " exit..");  
        connections.remove(inbound.getUser());  
    }  
      
    public static void sendMessageToUser(String user,String message){  
        try {  
            //向特定的用户发送数据  
            System.out.println("send message to user : " + user + " ,message content : " + message);  
            WebSocketMessageInbound inbound = connections.get(user);  
            if(inbound != null){  
                inbound.getWsOutbound().writeTextMessage(CharBuffer.wrap(message));  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
      
    //向所有的用户发送消息  
    public static void sendMessage(String message){  
        try {  
            Set<String> keySet = connections.keySet();  
            for (String key : keySet) {  
                WebSocketMessageInbound inbound = connections.get(key);  
                if(inbound != null){  
                    System.out.println("send message to user : " + key + " ,message content : " + message);  
                    inbound.getWsOutbound().writeTextMessage(CharBuffer.wrap(message));  
                }  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  
 

前台展示

 
    上面的代码就是聊天室后台的代码,主要是由3个对象组成,Servlet、连接对象、连接池,下面就是前台的代码,前台的代码主要是实现与服务器进行连接,展示用户列表及信息列表,前台的展示使用了Ext框架,不熟悉Ext的同学可以初步的了解下Ext,下面的是index.jsp的代码:
[html] view plaincopy
 
  1. <%@ page language="java" pageEncoding="UTF-8" import="com.ibcio.WebSocketMessageServlet"%>  
  2. <%  
  3.     String user = (String)session.getAttribute("user");  
  4.     if(user == null){  
  5.         //为用户生成昵称  
  6.         user = "游客" + WebSocketMessageServlet.ONLINE_USER_COUNT;  
  7.         WebSocketMessageServlet.ONLINE_USER_COUNT ++;  
  8.         session.setAttribute("user", user);  
  9.     }  
  10.     pageContext.setAttribute("user", user);  
  11. %>  
  12. <html>  
  13. <head>  
  14.     <title>WebSocket 聊天室</title>  
  15.     <!-- 引入CSS文件 -->  
  16.     <link rel="stylesheet" type="text/css" href="ext4/resources/css/ext-all.css">  
  17.     <link rel="stylesheet" type="text/css" href="ext4/shared/example.css" />  
  18.     <link rel="stylesheet" type="text/css" href="css/websocket.css" />  
  19.       
  20.     <!-- 映入Ext的JS开发包,及自己实现的webscoket. -->  
  21.     <script type="text/javascript" src="ext4/ext-all-debug.js"></script>  
  22.     <script type="text/javascript" src="websocket.js"></script>  
  23.     <script type="text/javascript">  
  24.         var user = "${user}";  
  25.     </script>  
  26. </head>  
  27.   
  28. <body>  
  29.     <h1>WebSocket聊天室</h1>  
  30.     <p>通过HTML5标准提供的API与Ext富客户端框架相结合起来,实现聊天室,有以下特点:</p>  
  31.     <ul class="feature-list" style="padding-left: 10px;">  
  32.         <li>实时获取数据,由服务器推送,实现即时通讯</li>  
  33.         <li>利用WebSocket完成数据通讯,区别于轮询,长连接等技术,节省服务器资源</li>  
  34.         <li>结合Ext进行页面展示</li>  
  35.         <li>用户上线下线通知</li>  
  36.     </ul>  
  37.     <div id="websocket_button"></div>  
  38. </body>  
  39. </html>  
      页面的展示主要是在websocket.js中进行控制,下面是websocket.jsd的代码:
 
 

相关推荐