设为首页 收藏本站
查看: 1091|回复: 0

[经验分享] 基于Tomcat7、Java、WebSocket的服务器推送聊天室

[复制链接]

尚未签到

发表于 2015-11-14 15:01:50 | 显示全部楼层 |阅读模式
前言

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

        Tomcat实现WebSocket的主要是依靠org.apache.catalina.websocket.MessageInbound这个类,这个类的在{TOMCAT_HOME}/lib/catalina.jar中,所以你开发的时候需要将catalina.jar和tomcat-coyote.jar引入进来,下面这段代码就是暴露给客户端连接地址的Servlet:
[java] viewplaincopy

  • 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类的代码:[java] viewplaincopy

  • 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连接池类,这个类是用来管理目前在线的用户的连接,下面是这个类的代码:[java] viewplaincopy

  • 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(&quot;user : &quot; &#43; inbound.getUser() &#43; &quot; join..&quot;);  
  •         connections.put(inbound.getUser(), inbound);  
  •     }  
  •       
  •     //获取所有的在线用户  
  •     public static Set<String> getOnlineUser(){  
  •         return connections.keySet();  
  •     }  
  •       
  •     public static void removeMessageInbound(WebSocketMessageInbound inbound){  
  •         //移除连接  
  •         System.out.println(&quot;user : &quot; &#43; inbound.getUser() &#43; &quot; exit..&quot;);  
  •         connections.remove(inbound.getUser());  
  •     }  
  •       
  •     public static void sendMessageToUser(String user,String message){  
  •         try {  
  •             //向特定的用户发送数据  
  •             System.out.println(&quot;send message to user : &quot; &#43; user &#43; &quot; ,message content : &quot; &#43; 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(&quot;send message to user : &quot; &#43; key &#43; &quot; ,message content : &quot; &#43; message);  
  •                     inbound.getWsOutbound().writeTextMessage(CharBuffer.wrap(message));  
  •                 }  
  •             }  
  •         } catch (IOException e) {  
  •             e.printStackTrace();  
  •         }  
  •     }  
  • }  

前台展示

    上面的代码就是聊天室后台的代码,主要是由3个对象组成,Servlet、连接对象、连接池,下面就是前台的代码,前台的代码主要是实现与服务器进行连接,展示用户列表及信息列表,前台的展示使用了Ext框架,不熟悉Ext的同学可以初步的了解下Ext,下面的是index.jsp的代码:[html] viewplaincopy

  • <%@ page language=&quot;java&quot; pageEncoding=&quot;UTF-8&quot; import=&quot;com.ibcio.WebSocketMessageServlet&quot;%>  
  • <%  
  •     String user = (String)session.getAttribute(&quot;user&quot;);  
  •     if(user == null){  
  •         //为用户生成昵称  
  •         user = &quot;游客&quot; &#43; WebSocketMessageServlet.ONLINE_USER_COUNT;  
  •         WebSocketMessageServlet.ONLINE_USER_COUNT &#43;&#43;;  
  •         session.setAttribute(&quot;user&quot;, user);  
  •     }  
  •     pageContext.setAttribute(&quot;user&quot;, user);  
  • %>  
  • <html>  
  • <head>  
  •     <title>WebSocket 聊天室</title>  
  •     <!-- 引入CSS文件 -->  
  •     <link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;ext4/resources/css/ext-all.css&quot;>  
  •     <link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;ext4/shared/example.css&quot; />  
  •     <link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;css/websocket.css&quot; />  
  •       
  •     <!-- 映入Ext的JS开发包,及自己实现的webscoket. -->  
  •     <script type=&quot;text/javascript&quot; src=&quot;ext4/ext-all-debug.js&quot;></script>  
  •     <script type=&quot;text/javascript&quot; src=&quot;websocket.js&quot;></script>  
  •     <script type=&quot;text/javascript&quot;>  
  •         var user = &quot;${user}&quot;;  
  •     </script>  
  • </head>  
  •   
  • <body>  
  •     <h1>WebSocket聊天室</h1>  
  •     <p>通过HTML5标准提供的API与Ext富客户端框架相结合起来,实现聊天室,有以下特点:</p>  
  •     <ul class=&quot;feature-list&quot; style=&quot;padding-left: 10px;&quot;>  
  •         <li>实时获取数据,由服务器推送,实现即时通讯</li>  
  •         <li>利用WebSocket完成数据通讯,区别于轮询,长连接等技术,节省服务器资源</li>  
  •         <li>结合Ext进行页面展示</li>  
  •         <li>用户上线下线通知</li>  
  •     </ul>  
  •     <div id=&quot;websocket_button&quot;></div>  
  • </body>  
  • </html>  
       页面的展示主要是在websocket.js中进行控制,下面是websocket.jsd的代码:
[javascript] viewplaincopy

  • //用于展示用户的聊天信息  
  • 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=&quot;l-im-message-warn&quot;>​交谈中请勿轻信汇款、中奖信息、陌生电话。 请遵守相关法律法规。</div>',  
  •             '<tpl for=&quot;.&quot;>',  
  •             '<div class=&quot;l-im-message&quot;>',  
  •             '<div class=&quot;l-im-message-header l-im-message-header-{source}&quot;>{from}  {timestamp}</div>',  
  •             '<div class=&quot;l-im-message-body&quot;>{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;  
  •         }  
  •     }  
  • });  
      这段代码主要是实现了展示消息的容器,下面就是页面加载完成后开始执行的代码:
[javascript] viewplaincopy

  • 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 &#43; '  (已连接)');  
  •                     }  
  •                     websocket.onerror = function() {  
  •                         //连接失败  
  •                         win.setTitle(title &#43; '  (连接发生错误)');  
  •                     }  
  •                     websocket.onclose = function() {  
  •                         //连接断开  
  •                         win.setTitle(title &#43; '  (已经断开连接)');  
  •                     }  
  •                     //消息接收  
  •                     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 = '欢迎您:' &#43; user;  
  •             //展示窗口  
  •             var win = Ext.create('Ext.window.Window', {  
  •                         title : title &#43; '  (未连接)',  
  •                         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就可以了,否则会包Couldnot initialize class com.ibcio.WebSocketMessageServlet错误,切记    如果还是无法建立连接,请下载最新的tomcat,忘了是那个版本的tomcatcreateWebSocketInbound是没有request参数的,现在的这个代码是有这个参数了,7.0.3XX版本都是带这个参数的,切记。
总结

       使用WebSocket开发服务器推送非常方便,这个是个简单的应用,其实还可以结合WebRTC实现视频聊天和语音聊天。在我的Leetop项目中已经实现了浏览器端视频聊天的功能,大家可以去看看 www.ibcio.com,在我的另外一篇文章中有详细的介绍:http://blog.iyunv.com/leecho571/article/details/8207102,下图就是Leetop项目的效果图: DSC0001.jpg

实例下载

下载地址:http://download.iyunv.com/detail/leecho571/5854569

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-139232-1-1.html 上篇帖子: Tomcat的java.lang.OutOfMemoryError异常 下篇帖子: Axis2和Tomcat6.0开发Web Service环境配置 测试和实例 整理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表