分享一個基於長鏈接+長輪詢+原生的JS及AJAX實現的多人在線即時交流聊天室

實現網頁版的在線聊天室的方法有不少,在沒有來到HTML5以前,常見的有:定時輪詢、長鏈接+長輪詢、基於第三方插件(如FLASH的Socket),而若是是HTML5,則比較簡單,能夠直接使用WebSocket,固然HTML5目前在PC端並無被全部瀏覽器支持,因此個人這個聊天室還是基於長鏈接+長輪詢+原生的JS及AJAX實現的多人在線即時交流聊天室,這個聊天室實際上是我上週週末完成的,功能簡單,可能有些不足,但能夠知足在線即時聊天需求,分享也是給你們提供一個思路,你們能夠基於此來實現更好的在線即時聊天工具。javascript

聊天室功能簡介:css

1。支持多人進入同一個聊天室聊天; html

2。進入即離線均會自動生成通知信息顯示在聊天室中,這樣聊天的人們就知道誰進來了誰離開了;前端

3。實時顯示在線人員表列;java

4。無需數據庫支持,所有存在內存中,固然有條件的能夠採用分佈式緩存或加一個數據庫來存,這裏演示就是用內存來存了。ajax

下面就開始分享個人代碼,因爲採用原生的JS及AJAX,因此簡單易懂,代碼分別WEB前端及服務端(有點廢話了)數據庫

WEB前端源代碼以下:(ChatPage.html)json

<!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。狀態切換:上線則加入到在線人員集合中,並生成一條上線消息放入到聊天消息集合中,離線則從在線人員集合中移除該人員信息,並生成一條離線消息放入聊天消息集合中;

注意事項,因爲採用了全局靜態集合,因此線程同步比較重要。

最終的實現效果展現以下:

 張三:

李四:

小美:

若是以爲不錯的話,給個推薦吧,你的支持是推進我不斷前進的動力及寫做的源泉,我一直堅持:知識在於分享,分享的同時本身也在成長,但願與你們共同成長,謝謝!

相關文章
相關標籤/搜索