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

前言

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

在这里实例中,实现了消息的实时推送,还实现了聊天用户的上下线通知。下面就开始具体讲解如何实现。

后台处理

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的代码:

<%@ page language="java" pageEncoding="UTF-8" import="com.ibcio.WebSocketMessageServlet"%>
<%
  String user = (String)session.getAttribute("user");
  if(user == null){
    //为用户生成昵称
    user = "游客" + WebSocketMessageServlet.ONLINE_USER_COUNT;
    WebSocketMessageServlet.ONLINE_USER_COUNT ++;
    session.setAttribute("user", user);
  }
  pageContext.setAttribute("user", user);
%>
<html>
<head>
  <title>WebSocket 聊天室</title>
  <!-- 引入CSS文件 -->
  <link rel="stylesheet" type="text/css" href="ext4/resources/css/ext-all.css">
  <link rel="stylesheet" type="text/css" href="ext4/shared/example.css" />
  <link rel="stylesheet" type="text/css" href="css/websocket.css" /> 

  <!-- 映入Ext的JS开发包,及自己实现的webscoket. -->
  <script type="text/javascript" src="ext4/ext-all-debug.js"></script>
  <script type="text/javascript" src="websocket.js"></script>
  <script type="text/javascript">
    var user = "${user}";
  </script>
</head> 

<body>
  <h1>WebSocket聊天室</h1>
  <p>通过HTML5标准提供的API与Ext富客户端框架相结合起来,实现聊天室,有以下特点:</p>
  <ul class="feature-list" style="padding-left: 10px;">
    <li>实时获取数据,由服务器推送,实现即时通讯</li>
    <li>利用WebSocket完成数据通讯,区别于轮询,长连接等技术,节省服务器资源</li>
    <li>结合Ext进行页面展示</li>
    <li>用户上线下线通知</li>
  </ul>
  <div id="websocket_button"></div>
</body>
</html>

页面的展示主要是在websocket.js中进行控制,下面是websocket.jsd的代码:

//用于展示用户的聊天信息
Ext.define('MessageContainer', { 

  extend : 'Ext.view.View', 

  trackOver : true, 

  multiSelect : false, 

  itemCls : 'l-im-message', 

  itemSelector : 'div.l-im-message', 

  overItemCls : 'l-im-message-over', 

  selectedItemCls : 'l-im-message-selected', 

  style : {
    overflow : 'auto',
    backgroundColor : '#fff'
  }, 

  tpl : [
      '<div class="l-im-message-warn">​交谈中请勿轻信汇款、中奖信息、陌生电话。 请遵守相关法律法规。</div>',
      '<tpl for=".">',
      '<div class="l-im-message">',
      '<div class="l-im-message-header l-im-message-header-{source}">{from} {timestamp}</div>',
      '<div class="l-im-message-body">{content}</div>', '</div>',
      '</tpl>'], 

  messages : [], 

  initComponent : function() {
    var me = this;
    me.messageModel = Ext.define('Leetop.im.MessageModel', {
          extend : 'Ext.data.Model',
          fields : ['from', 'timestamp', 'content', 'source']
        });
    me.store = Ext.create('Ext.data.Store', {
          model : 'Leetop.im.MessageModel',
          data : me.messages
        });
    me.callParent();
  }, 

  //将服务器推送的信息展示到页面中
  receive : function(message) {
    var me = this;
    message['timestamp'] = Ext.Date.format(new Date(message['timestamp']),
        'H:i:s');
    if(message.from == user){
      message.source = 'self';
    }else{
      message.source = 'remote';
    }
    me.store.add(message);
    if (me.el.dom) {
      me.el.dom.scrollTop = me.el.dom.scrollHeight;
    }
  }
});

这段代码主要是实现了展示消息的容器,下面就是页面加载完成后开始执行的代码:

  Ext.onReady(function() {
      //创建用户输入框
      var input = Ext.create('Ext.form.field.HtmlEditor', {
            region : 'south',
            height : 120,
            enableFont : false,
            enableSourceEdit : false,
            enableAlignments : false,
            listeners : {
              initialize : function() {
                Ext.EventManager.on(me.input.getDoc(), {
                      keyup : function(e) {
                        if (e.ctrlKey === true
                            && e.keyCode == 13) {
                          e.preventDefault();
                          e.stopPropagation();
                          send();
                        }
                      }
                    });
              }
            }
          });
      //创建消息展示容器
      var output = Ext.create('MessageContainer', {
            region : 'center'
          }); 

      var dialog = Ext.create('Ext.panel.Panel', {
            region : 'center',
            layout : 'border',
            items : [input, output],
            buttons : [{
                  text : '发送',
                  handler : send
                }]
          });
      var websocket; 

      //初始话WebSocket
      function initWebSocket() {
        if (window.WebSocket) {
          websocket = new WebSocket(encodeURI('ws://localhost:8080/WebSocket/message'));
          websocket.onopen = function() {
            //连接成功
            win.setTitle(title + ' (已连接)');
          }
          websocket.onerror = function() {
            //连接失败
            win.setTitle(title + ' (连接发生错误)');
          }
          websocket.onclose = function() {
            //连接断开
            win.setTitle(title + ' (已经断开连接)');
          }
          //消息接收
          websocket.onmessage = function(message) {
            var message = JSON.parse(message.data);
            //接收用户发送的消息
            if (message.type == 'message') {
              output.receive(message);
            } else if (message.type == 'get_online_user') {
              //获取在线用户列表
              var root = onlineUser.getRootNode();
              Ext.each(message.list,function(user){
                var node = root.createNode({
                  id : user,
                  text : user,
                  iconCls : 'user',
                  leaf : true
                });
                root.appendChild(node);
              });
            } else if (message.type == 'user_join') {
              //用户上线
                var root = onlineUser.getRootNode();
                var user = message.user;
                var node = root.createNode({
                  id : user,
                  text : user,
                  iconCls : 'user',
                  leaf : true
                });
                root.appendChild(node);
            } else if (message.type == 'user_leave') {
                //用户下线
                var root = onlineUser.getRootNode();
                var user = message.user;
                var node = root.findChild('id',user);
                root.removeChild(node);
            }
          }
        }
      }; 

      //在线用户树
      var onlineUser = Ext.create('Ext.tree.Panel', {
            title : '在线用户',
            rootVisible : false,
            region : 'east',
            width : 150,
            lines : false,
            useArrows : true,
            autoScroll : true,
            split : true,
            iconCls : 'user-online',
            store : Ext.create('Ext.data.TreeStore', {
                  root : {
                    text : '在线用户',
                    expanded : true,
                    children : []
                  }
                })
          });
      var title = '欢迎您:' + user;
      //展示窗口
      var win = Ext.create('Ext.window.Window', {
            title : title + ' (未连接)',
            layout : 'border',
            iconCls : 'user-win',
            minWidth : 650,
            minHeight : 460,
            width : 650,
            animateTarget : 'websocket_button',
            height : 460,
            items : [dialog,onlineUser],
            border : false,
            listeners : {
              render : function() {
                initWebSocket();
              }
            }
          }); 

      win.show(); 

      //发送消息
      function send() {
        var message = {};
        if (websocket != null) {
          if (input.getValue()) {
            Ext.apply(message, {
                  from : user,
                  content : input.getValue(),
                  timestamp : new Date().getTime(),
                  type : 'message'
                });
            websocket.send(JSON.stringify(message));
            //output.receive(message);
            input.setValue('');
          }
        } else {
          Ext.Msg.alert('提示', '您已经掉线,无法发送消息!');
        }
      }
    });

上面的代码就是页面完成加载后自动连接服务器,并创建展示界面的代码。

注意

需要注意的两点,在部署完成之后需要将在tomcat应用目录中的lib目录下的catalina.jar和tomcat-coyote.jar删掉,比如项目的lib目录在D:\workspace\WebSocket\WebRoot\WEB-INF\lib,而部署的应用lib目录是在D:\tools\apache-tomcat-7.0.32\webapps\WebSocket\WEB-INF\lib,删掉部署目录的lib目录中连两个jar就可以了,否则会包Could not initialize class com.ibcio.WebSocketMessageServlet错误,切记。

如果还是无法建立连接,请下载最新的tomcat,忘了是那个版本的tomcatcreateWebSocketInbound是没有request参数的,现在的这个代码是有这个参数了,7.0.3XX版本都是带这个参数的,切记。

总结

使用WebSocket开发服务器推送非常方便,这个是个简单的应用,其实还可以结合WebRTC实现视频聊天和语音聊天。

实例下载

下载地址:demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 用java WebSocket做一个聊天室

    最近一个项目中,需要用到Java的websocket新特性,于是就学了一下,感觉这技术还挺好玩的,瞬间知道网页上面的那些在线客服是怎么做的了. 先看图: 实现了多客户机进行实时通讯. 下面看代码项目结构图:很简单,就1个类,1个页面 然后看具体代码 先看后端代码 package com.main; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.

  • Java基于socket实现简易聊天室实例

    本文实例讲述了Java基于socket实现简易聊天室的方法.分享给大家供大家参考.具体实现方法如下: chatroomdemo.java package com.socket.demo; import java.io.IOException; import java.net.DatagramSocket; public class ChatRoomDemo { /** * @param args * @throws IOException */ public static void main(S

  • 基于java编写局域网多人聊天室

    由于需要制作网络计算机网络课程设计,并且不想搞网络布线或者局域网路由器配置等等这种完全搞不懂的东西,最后决定使用socket基于java编写一个局域网聊天室: 关于socket以及网络编程的相关知识详见我另一篇文章:Java基于socket编程 程序基于C/S结构,即客户端服务器模式. 服务器: 默认ip为本机ip 需要双方确定一个端口号 可设置最大连接人数 可启动与关闭 界面显示在线用户人以及姓名(本机不在此显示) 客户端: 需要手动设置服务器ip地址(局域网) 手动设置端口号 输入姓名 可连

  • Java Socket聊天室编程(二)之利用socket实现单聊聊天室

    在上篇文章Java Socket聊天室编程(一)之利用socket实现聊天之消息推送中我们讲到如何使用socket让服务器和客户端之间传递消息,达到推送消息的目的,接下来我将写出如何让服务器建立客户端与客户端之间的通讯. 其实就是建立一个一对一的聊天通讯. 与上一篇实现消息推送的代码有些不同,在它上面加以修改的. 如果没有提到的方法或者类则和上一篇一模一样. 1,修改实体类(服务器端和客户端的实体类是一样的) 1,UserInfoBean 用户信息表 public class UserInfoB

  • java实现一个简单TCPSocket聊天室功能分享

    本文实例为大家分享了java实现TCPSocket聊天室功能的相关代码,供大家参考,具体内容如下 1.TCPserver.java import java.net.*; import java.io.*; import java.util.*; import java.util.concurrent.*; public class TCPserver{ private static final int SERVERPORT = 8888; private ServerSocket MyServe

  • 使用Java和WebSocket实现网页聊天室实例代码

    在没介绍正文之前,先给大家介绍下websocket的背景和原理: 背景 在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持; flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax bridge,可以在javascript中使用这两项功能. 可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协议,能更

  • Java基于UDP协议实现简单的聊天室程序

    最近比较闲,一直在抽空回顾一些Java方面的技术应用. 今天没什么事做,基于UDP协议,写了一个非常简单的聊天室程序. 现在的工作,很少用到socket,也算是对Java网络编程方面的一个简单回忆. 先看一下效果: 实现的效果可以说是非常非常简单,但还是可以简单的看到一个实现原理.  "聊天室001"的用户,小红和小绿相互聊了两句,"聊天室002"的小黑无人理会,在一旁寂寞着. 看一下代码实现: 1.首先是消息服务器的实现,功能很简单: •将客户端的信息(进入了哪一

  • 使用java基于pushlet和bootstrap实现的简单聊天室

    这是一个简单的不能再简单的聊天室,本代码包含以下功能 1.用户注册. 2.用户登录. 3.当然还可以聊天. DBUtil.java 复制代码 代码如下: package com.hongyuan.core;   import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statemen

  • Java continue break制作简单聊天室程序

    Java continue break 制作简单聊天室程序,屏蔽不文明语言,显示每句话聊天时间 package com.swift; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; public class ChatWithBreakContinue { public static void main(String[] args) { Scanner scan = new Sc

  • Java Socket聊天室编程(一)之利用socket实现聊天之消息推送

    相关阅读:Java Socket聊天室编程(二)之利用socket实现单聊聊天室 网上已经有很多利用socket实现聊天的例子了,但是我看过很多,多多少有一些问题存在. 这里我将实现一个比较完整的聊天例子,并解释其中的逻辑. 由于socket这一块比较大,所以我将分出几篇来写一个比较完整的socket例子. 这里我们先来实现一个最简单的,服务器与客户端通讯,实现消息推送的功能. 目的:服务器与客户端建立连接,客户端可以向服务器发送消息,服务器可以向客户端推送消息. 1,使用java建立socke

随机推荐