`
yiminghe
  • 浏览: 1431673 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Extjs 聊天窗口 -续2 - http长连接的实现

阅读更多

认为长连接就是有个http请求被服务器阻塞了 ,这样的话浏览器就一直等在那,服务器可以随时给浏览器发送信息了,对于servlet 就是一个线程被阻塞在一个servlet实例那里,等待其他servlet线程的通知。


ps:一个servlet实例被无数个线程使用的,阻塞的线程在这个实例上排队


基于上述思想,实现实时聊天,客户端向一个receive.jsp发起一个 ajax 接受信息的请求,服务器判断有信息的话,就 ajax 处理后,再发送请求,否则 receive.jsp wait() ,等待。如果一个 ajax调用了 send.jsp ,则通知 receive.jsp notify 。还要用户退出时,也要 receive.jsp notify ,否则这个线程就永远阻塞了!这就需要sessionlistener

 

 

 

 

 

1.HttpSessionListener

 

用于记录当前在线用户 ,以及当前用户退出时通知其他用户

 

 

package hyjc.listener;

import hyjc.common.SequenceUtil;

import java.util.*;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

import org.apache.log4j.Logger;


public class CustomSessionListener implements HttpSessionListener, ServletContextListener {

    static Logger logger = Logger.getLogger(CustomSessionListener.class);

    private Hashtable allSessions = new Hashtable();
    //session销毁前需要通知的servlet线程实例
    private Map<String, Object> servers = Collections.synchronizedMap(new HashMap<String, Object>());

    public CustomSessionListener() {
        logger.debug("CustomSessionListener constructed!");
    }

    public void sessionCreated(HttpSessionEvent arg0) {
        HttpSession session = arg0.getSession();
        logger.debug("CustomSessionListener sessionCreated " + session.getId());
        allSessions.put(session.getId(), session);

    }

    public void sessionDestroyed(HttpSessionEvent arg0) {
        HttpSession session = arg0.getSession();
        logger.debug("CustomSessionListener sessionDestroyed " + session.getId());
        allSessions.remove(session.getId());
        Set<String> keys = servers.keySet();
        for (String key : keys) {
            logger.debug("CustomSessionListener notify " + key);
            Object o = servers.get(key);
            synchronized (o) {
                try {
                    o.notifyAll();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 应用关闭
     */
    public void contextDestroyed(ServletContextEvent sc) {
        ServletContext application = sc.getServletContext();
        logger.debug("CustomSessionListener contextDestroyed " + application.getServletContextName());
    }

    /**
     * 应用启动
     */
    public void contextInitialized(ServletContextEvent sc) {
        ServletContext application = sc.getServletContext();
        logger.debug("CustomSessionListener contextInitialized " + application.getServletContextName());
        application.setAttribute("allSessions", allSessions);
        application.setAttribute("_SESSIONSERVERLETLISTENSERS_", servers);
        application.setAttribute("contextInitializedTime", System.currentTimeMillis());
    }

}

 

2. receive.jsp

 

ajax 接收消息 ,当没有消息时线程阻塞

 

<%@ page contentType="text/plain; charset=GBK"%>
<%@ page import="java.util.Hashtable"%><%@ page import="java.util.Map"%>
<%
       boolean newM=false;

         //session销毁前需要通知的servlet线程实例
        Map<String, Object> servers =(Map<String, Object>)application.getAttribute("_SESSIONSERVERLETLISTENSERS_");
        if(servers.get("_UPDATECHATSERVLET_")==null)     {
            servers.put("_UPDATECHATSERVLET_",this);
         }


       Hashtable allSessions = (Hashtable) application.getAttribute("allSessions");
       while(!newM)   {
        //如果已经退出,自己建的全局session hashtable已没有该id,则 直接输出非法json,extjs 不会再连了
        if(allSessions.get(session.getId())==null) break;


        String im = (String)session.getAttribute("_IM_");
        //有消息就调用回调函数
        if (im != null)
        {
          out.println("\n{'msgs':[");
          out.println(im);
          out.println("\t]");
          session.setAttribute("_IM_", null);
          out.print("}");
          out.flush();
          newM=true;
        }
        //否则继续等待
        else
        {
        //必须必须同步
             synchronized (this)    {
              System.out.println("wait ******************************************s"+session.getId());
            try{
            //会释放lock
            wait();}catch (Exception e){
            e.printStackTrace();
            newM=true;
            }

            System.out.println("waked ******************************************s"+session.getId());
            }

        }

       }
%>

 

3.sendmsgLong.jsp

 

发送消息,并通知阻塞在接收消息的所有线程

 

<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="hyjc.common.ConversionUtil,java.sql.Timestamp,java.util.Hashtable" %>
<%@ page import="java.util.Map" %>
<%


    Timestamp now = new Timestamp(System.currentTimeMillis());

    String sender = request.getParameter("sender");

    if (!session.getId().equals(sender)) {
        out.println("{'result':'访问拒绝!'}");
        return;
    }

    Hashtable allSessions = (Hashtable) application.getAttribute("allSessions");
    String nickname = (String) session.getAttribute("_IM_NICKNAME_");
    /*
    // 对昵称进行检查
    String nickname = request.getParameter("nickname");

    nickname = nickname.replace("\\", "\\\\").replace("'", "\\'");

    // 2008.07.18 只有变化的时候才检查


    if (!nickname.equals(oldNickname)) {

        Object[] sessions = allSessions.values().toArray();
        for (Object s0 : sessions) {
            HttpSession s = (HttpSession) s0;
            if (s != session) {
                try {
                    String name = (String) s.getAttribute("_IM_NICKNAME_");
                    if (nickname.equals(name)) {
                        //out.println("{'result':'昵称已经存在,请修改!'}");
                        //return ;
                    }
                } catch (IllegalStateException ex) {
                }
            }
        }

        session.setAttribute("_IM_NICKNAME_", nickname);
    }
    */
    String content = request.getParameter("content");

    String receivers = request.getParameter("receivers");

    if ("_IM_".equals(receivers)) {

        out.println("{'result':'ok'}");

        return;

    }

    String[] sessionIdList = receivers.split(",");

    String cur = "\t\t{\n"
            + "\t\t\t'sender':'" + sender + "',\n"
            + "\t\t\t'nickname':'" + nickname + "',\n"        // 2008.07.23
            + "\t\t\t'time':'" + ConversionUtil.toEmpty(now) + "',\n"
            + "\t\t\t'content':'" + content.replace("\\", "\\\\").replace("'", "\\'").replace("\n", "\\n").replace("\r", "") + "',\n"
            + "\t\t\t'receivers':[";

    int n = 0;
    for (String sid : sessionIdList) {
        HttpSession s = (HttpSession) allSessions.get(sid);
        if (s != null) {
            if (n != 0) cur += ",";
            cur += "'" + sid + "'";
            ++n;
        }
    }

    cur += "]\n"
            + "\t\t}\n";

    if (n == 0) {
        out.println("{'result':'接收者不在线!'}");
        return;
    }


    for (String sid : sessionIdList) {
        HttpSession s = (HttpSession) allSessions.get(sid);
        if (s != null && s != session) {
            String im = (String) s.getAttribute("_IM_");
            if (im == null) {
                im = cur;
            } else {
                im = im + "\t\t," + cur;
            }
            try {
                s.setAttribute("_IM_", im);
            } catch (IllegalStateException ex) {
            }
        }
    }

    // 对消息进行监控
    String idmon = (String) application.getAttribute("_IM_MONITOR_");
    if (idmon != null) {
        HttpSession s = (HttpSession) allSessions.get(idmon);
        if (s != null) {
            if (s != session) {    // 自己发的消息不需要保存
                try {
                    String im = (String) s.getAttribute("_IM_");
                    if (im == null) {
                        im = cur;
                    } else {
                        im = im + "\t\t," + cur;
                    }
                    s.setAttribute("_IM_", im);
                } catch (IllegalStateException ex) {
                }
            }
        } else {
            application.removeAttribute("_IM_MONITOR_");
        }
    }

    out.println("{");

    out.println("\t'result':'ok',success:true,");

    out.println("\t'cur':" + cur + ",");


    out.println("\t'msgs':[");
    /*
	String im = (String) session.getAttribute("_IM_");
	if (im != null) {
		out.println(im);
		session.setAttribute("_IM_", null);
	}*/
    out.println("\t],");

    out.println("\t'dummy':''");

    out.println("}");


    Map<String, Object> servers = (Map<String, Object>) application.getAttribute("_SESSIONSERVERLETLISTENSERS_");
    Object o = servers.get("_UPDATECHATSERVLET_");
    //必须必须同步,唤醒 等待接受消息的servlet线程实例
    synchronized (o) {
        o.notifyAll();
    }
    System.out.println("notified ******************************************s");

%>
 

4. chatWinLong.js

 

聊天引擎,只要访问一个长连jsp,返会处理后重新连接即可

 

Ext.onReady(function () {
    var chatWin = new Ext.Window({
        width: 800,
        height: 500,
        title: 'Ext聊天窗口测试版',
        renderTo: document.body,
        border: false,
        hidden: true,
        layout: 'border',
        closeAction: 'hide',
        collapsible: true,
        constrain: true,
        iconCls: 'my-userCommentIcon',
        maximizable: true,
        items: [{
            region: 'west',
            id: 'chat-west-panel',
            title: '用户面板',
            split: true,
            width: 170,
            minSize: 100,
            maxSize: 200,
            collapsible: true,
            constrain: true,
            //margins:'0 0 0 5',
            layout: 'accordion',
            layoutConfig: {
                animate: true
            },
            items: [{
                items: new Ext.tree.TreePanel({
                    id: 'im-tree',
                    rootVisible: false,
                    lines: false,
                    border: false,
                    dataUrl: 'chat/getUserFirst.jsp',
                    singleExpand: true,
                    selModel: new Ext.tree.MultiSelectionModel(),
                    root: new Ext.tree.AsyncTreeNode({
                        text: 'Online',
                        children: [{
                            text: 'Sunrise',
                            id: 'SunriseIm',
                            nodeType: 'async',
                            singleClickExpand: true,
                            expandable: true,
                            expanded: true
                        }]
                    })
                }),
                title: '在线人员',
                //layout:'form',
                border: false,
                autoScroll: true,
                iconCls: 'im_list',
                tools: [{
                    id: 'refresh',
                    qtip: '刷新在线信息',
                    // hidden:true,
                    handler: function (event, toolEl, panel) {
                        imRootNode.reload();
                        //reloadUser();
                    }
                },
                {
                    id: 'close',
                    qtip: '清除选定',
                    // hidden:true,
                    handler: function (event, toolEl, panel) {
                        Ext.getCmp('im-tree').getSelectionModel().clearSelections();
                    }
                }]
            },
            {
                title: 'Settings',
                html: '<p>Some settings in here.</p>',
                border: false,
                iconCls: 'settings'
            }]
        },
        {
            region: 'center',
            layout: 'border',
            items: [{
                region: 'center',
                title: '历史记录  ',
                id: 'history_panel',
                autoScroll: true,
                iconCls: 'my-userCommentIcon',
                tools: [{
                    id: 'refresh',
                    qtip: '注意:如果长时间没有收到对方回应,试一下',
                    // hidden:true,
                    handler: function (event, toolEl, panel) {
                        // refresh logic
                    }
                }]
            },
            {
                region: 'south',
                title: '聊天啦',
                layout: 'fit',
                iconCls: 'user_edit',
                autoScroll: true,
                height: 200,
                collapsible: true,
                //margins:'0 0 0 0',
                items: {
                    xtype: 'form',
                    baseCls: 'x-plain',
                    autoHeight: true,
                    autoWidth: true,
                    bodyStyle: 'padding:10 10px 0;',
                    defaults: {
                        anchor: '95%'
                    },
                    items: [{
                        xtype: 'htmleditor',
                        height: 130,
                        id: 'htmleditor',
                        hideLabel: true
                    }]
                },
                bbar: [{
                    text: '发送请输入Ctrl-Enter',
                    handler: function () {
                        sendmsg();
                    },
                    iconCls: 'my-sendingIcon'
                },
                '-', {
                    text: '清除',
                    handler: function () {
                        Ext.getCmp("htmleditor").reset();
                    }
                }]
            }]
        }]
    });
    var tree = Ext.getCmp('im-tree');
    var imRootNode = tree.getNodeById('SunriseIm');
    var query = location.search.substring(1); //获取查询串
    var sessionId = SESSION; //Ext.urlDecode(query).sid;
    // 发送消息


    function sendmsg() {
        Ext.getCmp("htmleditor").syncValue();
        var content_value = Ext.getCmp("htmleditor").getValue();
        if (content_value.trim() == '') {
            alert("您没有输入消息文本内容!");
            Ext.getCmp("htmleditor").focus(true);
            return;
        }
        var receivers_values = [];
        var tree = Ext.getCmp('im-tree');
        var receivers = tree.getSelectionModel().getSelectedNodes();
        for (var i = 0; i < receivers.length; ++i) {
            receivers_values.push(receivers[i].attributes.sessionId);
        }
        if (receivers_values.length == 0) {
            alert("您没有选择接收者!");
            tree.focus();
            return;
        }
        //alert(receivers_values.length);
        if (receivers_values.length > 1) {
            if (!confirm("您选择了多个接收者,是否继续?")) {
                return;
            }
        }
        var nickname_value = 'forget';
        var pars = {
            "content": content_value,
            "receivers": "" + receivers_values,
            "sender": sessionId
            // "nickname":'forget'
        };
        var conn = new Ext.data.Connection();
        // 发送异步请求
        conn.request({
            // 请求地址
            url: 'chat/sendmsgLong.jsp',
            method: 'post',
            params: pars,
            // 指定回调函数
            callback: msgsent
        });
    }
    function msgsent(options, success, response) {
        requestCount--;
        if (success) {
            try {
                var jsonObj = Ext.util.JSON.decode(response.responseText);
            } catch(e) {}
            if (jsonObj && jsonObj.success) {
                var cur = jsonObj.cur;
                var sessions = [];
                var c = imRootNode.childNodes;
                for (var i = 0; i < c.length; i++) {
                    sessions[c[i].attributes.sessionId] = c[i].attributes;
                    //alert(c[i].attributes.sessionId);
                }
                if (cur) {
                    var a = [];
                    for (var j = 0; j < cur.receivers.length; j++) {
                        //alert(cur.receivers[j]);
                        a.push(sessions[cur.receivers[j]].loginName);
                    }
                    var msg = '<div style="margin:20px 5px 10px 5px">   <img src="js/ext/user_comment.png"/> {0} <b>{1}</b> 对 <b>{2}</b> 说:<br> </div>';
                    var chat_record = new Ext.Element(document.createElement('div'));
                    chat_record.addClass('chat_record');
                    chat_record.update('<span style="margin:0px 5px 0px 5px">' + cur.content + '</span>');
                    Ext.getCmp("history_panel").body.appendChild(chat_record);
                    var canvas = new Ext.Element(document.createElement('canvas'));
                    var size_chat = chat_record.getSize();
                    if (!Ext.isIE && size_chat.height < 100) {
                        chat_record.setHeight(100);
                        size_chat.height = 100;
                    }
                    canvas.setSize(size_chat.width - 30, size_chat.height);
                    //canvas.setSize(size_chat.width-,40);
                    chat_record.appendChild(canvas);
                    if (window['G_vmlCanvasManager']) {
                        G_vmlCanvasManager.initElement(canvas.dom);
                    }
                    draw_m(chat_record.dom.lastChild, '#FFB100');
                    var mc = String.format(msg, cur.time, sessions[cur.sender].loginName, a);
                    Ext.getCmp("history_panel").body.insertHtml('beforeEnd', mc);
                    Ext.getCmp("history_panel").body.scroll('b', 10000, {
                        duration: 0.1
                    });
                }
                Ext.getCmp("htmleditor").reset();
            } else if (response.responseText.trim()) alert(response.responseText);
        } else {
            if (response.responseText.trim()) alert(response.responseText);
        }
    }
    //event for source editing mode
    new Ext.KeyMap(Ext.getCmp("htmleditor").getEl(), [{
        key: 13,
        ctrl: true,
        stopEvent: true,
        fn: sendmsg
    }]);
    //event for normal mode
    Ext.getCmp("htmleditor").onEditorEvent = function (e) {
        this.updateToolbar();
        var keyCode = (document.layers) ? keyStroke.which : e.keyCode;
        if (keyCode == 13 && e.ctrlKey) sendmsg(); //it'a my handler
    }
    var requestCount = 0;
    function getMsgs() {
        var conn = new Ext.data.Connection({
            timeout: 24 * 3600 * 1000
        });
        // 发送异步请求
        conn.request({
            // 请求地址
            url: 'chat/updateChatLong.jsp',
            method: 'post',
            // 指定回调函数
            callback: getMsgsCallback
        });
    }
    function getUsers() {
        var conn = new Ext.data.Connection({
            timeout: 24 * 3600 * 1000
        });
        // 发送异步请求
        conn.request({
            // 请求地址
            url: 'chat/getUserLong.jsp',
            method: 'post',
            // 指定回调函数
            callback: getUserLongCallback
        });
    }
    function getUserLongCallback(options, success, response) {
        if (success) {
            try {
                var jsonObj = Ext.util.JSON.decode(response.responseText);
            } catch(e) {}
            if (jsonObj) {
                //不是退出时notify
                if (jsonObj.nodes) {
                    imRootNode.reload();
                    getUsers();
                }
            }
        } else {
            if (response.responseText.trim()) alert(response.responseText);
        }
    }
    //回调函数


    function getMsgsCallback(options, success, response) {
        if (success) {
            try {
                var jsonObj = Ext.util.JSON.decode(response.responseText);
            } catch(e) {}
            if (jsonObj) {
                var msgs = jsonObj.msgs;
                var msg = '<div style="margin:20px 5px 10px 5px">   <img src="js/ext/user_comment.png"/> {0} <b>{1}</b> 对 <b>{2}</b> 说:<br> </div>';
                var sessions = [];
                var c = imRootNode.childNodes;
                for (var i = 0; i < c.length; i++) {
                    sessions[c[i].attributes.sessionId] = c[i].attributes;
                }
                if (msgs) {
                    for (var i = 0; i < msgs.length; i++) {
                        var a = [];
                        for (var j = 0; j < msgs[i].receivers.length; j++) {
                            a.push(sessions[msgs[i].receivers[j]].loginName);
                        }
                        var chat_record = new Ext.Element(document.createElement('div'));
                        chat_record.addClass('chat_record');
                        chat_record.update('<span style="margin:0px 5px 0px 5px">' + msgs[i].content + '</span>');
                        Ext.getCmp("history_panel").body.appendChild(chat_record);
                        var canvas = new Ext.Element(document.createElement('canvas'));
                        var size_chat = chat_record.getSize();
                        if (!Ext.isIE && size_chat.height < 100) {
                            chat_record.setHeight(100);
                            size_chat.height = 100;
                        }
                        canvas.setSize(size_chat.width - 10, size_chat.height);
                        //canvas.setSize(size_chat.width-,40);
                        chat_record.appendChild(canvas);
                        if (window['G_vmlCanvasManager']) {
                            G_vmlCanvasManager.initElement(canvas.dom);
                        }
                        draw_m(chat_record.dom.lastChild, '#FFB100');
                        var mc = String.format(msg, msgs[i].time, sessions[msgs[i].sender].loginName, a);
                        Ext.getCmp("history_panel").body.insertHtml('beforeEnd', mc);
                        Ext.getCmp("history_panel").body.scroll('b', 10000, {
                            duration: 0.1
                        });
                    }
                    if (!chatWin.isVisible()) {
                        self.focus();
                        Ext.example.msg('叮当', '您有新的短消息     <a href="javascript:window.startChatWin()">查看</a>');
                    }
                    getMsgs();
                }
            } else if (response.responseText.trim()) alert(response.responseText);
        } else {
            if (response.responseText.trim()) alert(response.responseText);
        }
    }
    //chatWin.show();
    //chatWin.setSize(0,0);
    //chatWin.hide();
    if (!Ext.isIE) {
        chatWin.collapse();
    }
    /*
     var chatTask = {
     run:reloadUser,
     //scope:this,
     interval: 5000 //1 second
     };
     time_pro = new Ext.util.TaskRunner();
     time_pro.start(chatTask);
     */
    //长连接方式
    getMsgs();
    //长连接方式
    getUsers();
    //chatWin.hide();
    window.startChatWin = function () {
        chatWin.show();
        chatWin.center();
        //Ext.getCmp('htmleditor').focus();
    };
    function draw_m(canvas, color) {
        var context = canvas.getContext("2d");
        var width = canvas.width;
        var height2 = canvas.height - 4.5;
        var height = canvas.height;
        context.beginPath();
        context.strokeStyle = color;
        context.moveTo(0.5, 0.5 + 5);
        context.arc(5.5, 5.5, 5, -Math.PI, -Math.PI / 2, false);
        context.lineTo(width - 0.5 - 5, 0.5);
        context.arc(width - 0.5 - 5, 5.5, 5, -Math.PI / 2, 0, false);
        context.lineTo(width - 0.5, height2 - 5);
        context.arc(width - 0.5 - 5, height2 - 5, 5, 0, Math.PI / 2, false);
        context.lineTo(width / 2 + 3, height2);
        context.lineTo(width / 2, height);
        context.lineTo(width / 2 - 3, height2);
        context.lineTo(0.5 + 5, height2);
        context.arc(0.5 + 5, height2 - 5, 5, Math.PI / 2, Math.PI, false);
        context.lineTo(0.5, 0.5 + 5);
        context.stroke();
    }
});
   

 

 

图中可以看到:updateChatlong.jsp 一直在 load 状态 ,因为服务器端 wait 了,在等待send.jsp notify,这样反映速度就很快了。

 

 

 

 

ps : pushlet简介

 

http://www.ibm.com/developerworks/cn/web/wa-lo-comet/

 

Extjs 聊天窗口 -续3 用pushlet来实现

 

 

  • 大小: 39.9 KB
  • 大小: 139.2 KB
分享到:
评论
3 楼 yiminghe 2010-01-16  
lijihuai 写道
不过这样有个问题啊,客户发过去的请求要一个个排队等待被处理了

建议使用 pushlet : http://yiminghe.iteye.com/blog/300050
2 楼 lijihuai 2010-01-15  
不过这样有个问题啊,客户发过去的请求要一个个排队等待被处理了
1 楼 lijihuai 2010-01-15  
哈哈哈,不错啊,得到了启发啊,很好的启发呢

相关推荐

Global site tag (gtag.js) - Google Analytics