首页 > 编程

分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室

2015-11-21 23:25:23 分类: 编程

实现网页版的在线聊天室的方法有很多,在没有来到HTML5之前,常见的有:定时轮询、长连接+长轮询、基于第三方插件(如FLASH的Socket),而如果是HTML5,则比较简单,可以直接使用WebSocket,当然HTML5目前在PC端并没有被所有浏览器支持,所以我的这个聊天室仍是基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室,这个聊天室其实是我上周周末完成的,功能简单,可能有些不足,但可以满足在线即时聊天需求,分享也是给大家提供一个思路,大家可以基于此来实现更好的在线即时聊天工具。

聊天室功能简介:

1。支持多人进入同一个聊天室聊天; 

2。进入即离线均会自动生成通知信息显示在聊天室中,这样聊天的人们就知道谁进来了谁离开了;

3。实时显示在线人员表列;

4。无需数据库支持,全部存在内存中,当然有条件的可以采用分布式缓存或加一个数据库来存,这里演示就是用内存来存了。

下面就开始分享我的代码,由于采用原生的JS及AJAX,所以简单易懂,代码分别WEB前端及服务端(有点废话了)

WEB前端源代码如下:(ChatPage.html)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <style type="text/css">
        html, body {
            margin: 0px;
            padding: 0px;
            width: 100%;
            height: 100%;
            background-color: #f8f7f7;
            font-family: arial,sans-serif;
        }

        #layouttable {
            margin:0px;
            padding:0px;
            width:100%;
            height:100%;
            border:2px solid green;
            border-collapse:collapse;
            min-width:800px;
        }

            #layouttable td {
                border: 1px solid green;
            }

        .h100p {
            height:100%;
        }

        .midtr{height:auto;}
            .midtr tr td {
                height: 100%;
            }

        #chatmsgbox, #chatonlinebox {
            background-color:white;
            overflow-x: hidden;
            overflow-y: auto;
            overflow-wrap: break-word;
            height: 100%;
        }

        #chatonlinebox {
            background-color:#f5d0a8;
        }

        .rc, .sd {
            overflow:hidden;
        }

         .rc p {
            float: left;
            color: green;
        }
            .sd p {
                float: right;
                color: orange;
            }
    </style>

</head>
<body>
    <table id="layouttable">
        <colgroup>
            <col style="width:auto" />
            <col style="width: 200px;" />
        </colgroup>
        <tr style="height:30px; background-color:lightblue;color:yellow;">
            <td>
                欢迎进入梦在旅途的网页即时在线大众聊天室 - www.zuowenjun.cn:
            </td>
            <td>
                当前在线人员
            </td>
        </tr>
        <tr style="height:auto;" id="midtr">
            <td>
                <div id="chatmsgbox">
                </div>
            </td>
            <td>
                <div id="chatonlinebox">
                    <ul id="chatnames"></ul>
                </div>
            </td>
        </tr>
        <tr style="height:50px;">
            <td colspan="2">
                <label for="name">聊天妮称:</label>
                <input type="text" id="name" style="width:80px;" />
                <input type="button" id="btnsavename" value="确认进入" />
                <label for="msg">输入内容:</label>
                <input type="text" id="msg" style="width:400px;" />
                <input type="button" id="btnSend" value="发送消息" disabled="disabled" />
            </td>
        </tr>
    </table>
    <script type="text/javascript">
        var chatName = null;
        var oChatmsgbox, oMsg, oChatnames;
        var ajaxforSend, ajaxforRecv;

        //页面加载初始化
        window.onload = function () {
            document.getElementById("btnsavename").onclick = function () {
                this.disabled = true;
                var oName = document.getElementById("name");
                oName.readOnly = true;
                document.getElementById("btnSend").disabled = false;
                //receiveMsg();
                setChatStatus(oName.value,"on");
            }

            document.getElementById("btnSend").onclick = function () {
                sendMsg(oMsg.value);
            };

            //init
            oChatmsgbox = document.getElementById("chatmsgbox");
            oMsg = document.getElementById("msg");
            oChatnames = document.getElementById("chatnames");
            ajaxforSend = getAjaxObject();
            ajaxforRecv = getAjaxObject();
        }

        //离开时提醒
        window.onbeforeunload = function () {
            event.returnValue = "您确定要退出聊天室吗?";
        }

        //关闭时离线
        window.onunload = function () {
            setChatStatus(chatName, "off");
        }

        //设置聊天状态:在线 OR 离线
        function setChatStatus(name, status) {
            callAjax(getAjaxObject(), "action=" + status + "&name=" + name, function (rs) {
                if (!rs.success) {
                    alert(rs.info);
                    return;
                }
                if (status == "on") {
                    chatName = document.getElementById("name").value;
                    setTimeout("receiveMsg()",500);
                }
                loadOnlineChatNames();
            });
        }

        //加载在线人员名称列表
        function loadOnlineChatNames(){
            callAjax(getAjaxObject(), "action=onlines", function (rs) {
                var lis = "";
                for(var i=0;i<rs.length;i++)
                {
                    lis += "<li>"+ rs[i] +"</li>";
                }
                oChatnames.innerHTML = lis;
            });
        }

        //接收消息列表
        function receiveMsg() {
            callAjax(ajaxforRecv, "action=receive&name=" + chatName, function (rs) {
                if (rs.success) {
                    showChatMsgs(rs.msgs, "rc");
                }
                setTimeout("receiveMsg()", 500);
            });
        }
        //发送消息
        function sendMsg(msg) {
            callAjax(ajaxforSend, "action=send&name=" + chatName + "&msg=" + escape(msg), function (rs) {
                if (rs.success) {
                    showChatMsgs(rs.msgs, "sd");
                    oMsg.value = null;
                    //alert("发送成功!");
                }
            });
        }

        //显示消息
        function showChatMsgs(msgs, cssClass) {
            var loadonline = false;
            for (var i = 0; i < msgs.length; i++) {
                var msg = msgs[i];
                oChatmsgbox.innerHTML += "<div class='" + cssClass + "'><p>[" + msg.name + "] - " + msg.sendtime + " 说:<br/>" + msg.content + "</p></div>";
                if (msg.type == "on" || msg.type == "off")
                {
                    loadonline = true;
                }
            }
            if (loadonline)
            {
                loadOnlineChatNames();
            }
        }

        //调用AJAX
        function callAjax(ajax, param, callback) {

            ajax.open("post", "ChatHandler.ashx", true);
            ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            ajax.onreadystatechange = function () {
                if (ajax.readyState == 4 && ajax.status == 200) {
                    var json = eval("(" + ajax.responseText + ")");
                    callback(json);
                }
            };
            ajax.send(param);
        }

        //获取AJAX对象(XMLHttpRequest)
        function getAjaxObject() {
            var xmlhttp;
            if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
                xmlhttp = new XMLHttpRequest();
            }
            else {// code for IE6, IE5
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            return xmlhttp;
        }

    </script>
</body>
</html>

代码很简单,并都有注释,在此就不作说明了,如果有疑问欢迎在下方评论。

服务端(ChatHandler.ashx) 

<%@ WebHandler Language="C#" Class="ChatHandler" %>

using System;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Threading;
using System.Collections.Concurrent;

public class ChatHandler : IHttpHandler
{

    private class Msg
    {
        public string name { get; set; }
        public string sendtime { get; set; }
        public string content { get; set; }
        public string readednams { get; set; }
        public int readedCount { get; set; }
        public string type { get; set; }
    }

    private static List<Msg> msgs = new List<Msg>();
    private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static object syncObject = new object(),syncObject1 = new object();
    private static List<string> onLineNames = new List<string>();

    public void ProcessRequest(HttpContext context)
    {
        string chatName = context.Request.Form["name"];
        string msg = context.Request.Form["msg"];
        string actionName = context.Request.Form["action"];
        JavaScriptSerializer jsSerializer = new JavaScriptSerializer();

        object responseObject = null;

        switch (actionName)
        {
            case "receive":
                {
                    responseObject = GetNewMessages(chatName);
                    break;
                }
            case "send":
                {
                    responseObject = SendMessage(chatName, msg, "normal");
                    break;
                }
            case "on":
            case "off":
                {
                    responseObject = SetChatStatus(chatName, actionName);
                    break;
                }
            case "onlines":
                {
                    responseObject = onLineNames;
                    break;
                }
        }

        context.Response.ContentType = "text/json";
        context.Response.Write(jsSerializer.Serialize(responseObject));

    }

    private object SetChatStatus(string chatName, string status)
    {
        if (status == "on")
        {
            if (onLineNames.Exists(s => s == chatName))
            {
                return new { success = false, info = "该聊天妮称已经存在,请更换一个名称吧!" };
            }
            lock (syncObject1)
            {
                onLineNames.Add(chatName);
            }
            SendMessage(chatName, "大家好,我进入聊天室了!", status);
            return new { success = true, info = string.Empty };
        }
        else
        {
            lock (syncObject1)
            {
                onLineNames.Remove(chatName);
            }
            SendMessage(chatName, "再见,我离开聊天室了!", status);
            return new { success = true, info = string.Empty };
        }
    }

    /// <summary>
    /// 获取未读的新消息
    /// </summary>
    /// <param name="chatName"></param>
    /// <returns></returns>
    private object GetNewMessages(string chatName)
    {
        //第一种:循环处理
        while (true)
        {

            var newMsgs = msgs.Where(m => m.name != chatName && !(m.readednams ?? "").Contains(chatName)).OrderBy(m => m.sendtime).ToList();
            if (newMsgs != null && newMsgs.Count() > 0)
            {
                lock (syncObject)
                {
                    newMsgs.ForEach((m) =>
                    {
                        m.readednams += chatName + ",";
                        m.readedCount++;
                    });
                    int chatNameCount = onLineNames.Count();
                    msgs.RemoveAll(m => m.readedCount >= chatNameCount);
                }

                return new { success = true, msgs = newMsgs };
            }

            Thread.Sleep(1000);
        }


        //第二种方法,采用自旋锁
        //List<Msg> newMsgs = null;
        //SpinWait.SpinUntil(() =>
        //{
        //    newMsgs = msgs.Where(m => m.name != chatName && !(m.readednams ?? "").Contains(chatName)).OrderBy(m => m.sendtime).ToList();
        //    return newMsgs.Count() > 0;
        //}, -1);

        //rwLock.EnterWriteLock();
        //newMsgs.ForEach(m =>
        //{
        //    m.readednams += chatName + ",";
        //    m.readedCount++;
        //});
        //rwLock.ExitWriteLock();
        //return new { success = true, msgs = newMsgs };
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="chatName"></param>
    /// <param name="msg"></param>
    /// <returns></returns>
    private object SendMessage(string chatName, string msg, string type)
    {
        var newMsg = new Msg() { name = chatName, sendtime = DateTime.Now.ToString("yyyy/MM/dd HH:mm"), content =HttpContext.Current.Server.HtmlEncode(msg), readednams = null, type = type };
        //rwLock.EnterWriteLock();
        lock (syncObject)
        {
            msgs.Add(newMsg);
        }
        //rwLock.ExitWriteLock();
        return new { success = true, msgs = new[] { newMsg } };
    }



    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

}

代码也相对简单,实现原理主要是:

1。聊天消息:循环获取未读的消息,在取出读的消息同时,将其标识为已读,全部已读的消息则删除;--我这里采用了两种方法,第二种方法被注释掉了,大家可以取消注释试试,也是不错的,比第一种更直观,建议使用;

2。发送消息:实例化一个消息实例并加入到聊天消息集合中;

3。状态切换:上线则加入到在线人员集合中,并生成一条上线消息放入到聊天消息集合中,离线则从在线人员集合中移除该人员信息,并生成一条离线消息放入聊天消息集合中;

注意事项,由于采用了全局静态集合,所以线程同步比较重要。

最终的实现效果展示如下:

 张三:

李四:

小美:

如果觉得不错的话,给个推荐吧,你的支持是推动我不断前进的动力及写作的源泉,我一直坚持:知识在于分享,分享的同时自己也在成长,希望与大家共同成长,谢谢!

参考手册

W3c0.com 提供的内容仅用于培训。我们不保证内容的正确性。通过使用本站内容随之而来的风险与本站无关。W3c0 简体中文版的所有内容仅供测试,对任何法律问题及风险不承担任何责任。 当使用本站时,代表您已接受了本站的使用条款和隐私条款。版权所有,保留一切权利。 鲁ICP备15022115号