65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET 2.0 和 AJAX 的多用户聊天室

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (41投票s)

2007 年 2 月 28 日

4分钟阅读

viewsIcon

451610

downloadIcon

16110

描述了如何使用 ASP.NET 2.0 和 AJAX 扩展构建多用户聊天室

Screenshot - chat.jpg

引言

构建交互式聊天室需要保持用户了解其他用户的消息和状态。使用 ASP.NET AJAX 实现聊天室将消除不必要的 post back,并为用户提供无缝的聊天体验。

背景

您可以阅读我以前的文章,了解如何构建一对一聊天室,本文是使用 ASP.NET 1.1 和第三方 AJAX 库构建的。这里使用了大部分聊天逻辑(房间、用户、消息发送等)。

代码演练

App_Code 文件夹包含所有将用于创建聊天应用程序的类。让我们从 ChatUser 开始

//ChatUser.cs

public class ChatUser:IDisposable
{
    #region Members
    public string UserID;
    public string UserName;
    public bool IsActive;
    public DateTime LastSeen;
    public int LastMessageReceived;
    #endregion 

    #region Constructors
    public ChatUser(string id,string userName)
    {
        this.UserID=id;
        this.IsActive=false;
        this.LastSeen=DateTime.MinValue ;
        this.UserName=userName;
        this.LastMessageReceived=0;
    }
    #endregion 

    #region IDisposable Members
    public void Dispose()
    {
        this.UserID="";
        this.IsActive=false;
        this.LastSeen=DateTime.MinValue ;
        this.UserName="";
        this.LastMessageReceived=0;
    }
    #endregion
}

此类表示将加入聊天室的用户。我们感兴趣的属性是 IsActiveLastSeenLastMessageReceived。请注意,在您的应用程序中,您可能正在使用 ASP.NET 成员资格提供程序。您可以轻松地将其与我们的聊天应用程序一起使用。您会发现 MembershipUser 类包含两个属性 IsOnlineLastActivityDate,可以分别用于替换属性 IsActiveLastSeen

聊天室中出现的每条消息都由以下类表示

//ChatMessage.cs

public class Message 
{
    #region Members
    public string user;
    public string msg;
    public MsgType type;
    #endregion 

    #region Constructors
    public Message(string _user, string _msg, MsgType _type) 
    {
        user = _user;
        msg = _msg;
        type = _type;
    }
    public Message(string _user, MsgType _type) : 
                this(_user, "", _type) { }
    public Message(MsgType _type) : this("", "", _type) { }
    #endregion 

    #region Methods
    public override string ToString()
    {
        switch(this.type)
        {
            case MsgType.Msg:
            return this.user+" says: "+this.msg;
            case MsgType.Join :
            return this.user + " has joined the room";
            case MsgType.Left :
            return this.user + " has left the room";
        }
        return "";
    }
    #endregion 
}

public enum MsgType { Msg, Start, Join, Left, Action}

每个聊天室都包含一个用户哈希表和一个消息列表。

//ChatRoom.cs

public class ChatRoom : IDisposable
{
    #region Members
    
    public List<message /> messages = null;
    public string RoomID;
    private Dictionary<string,chatuser /> RoomUsers;
    private int userChatRoomSessionTimeout;
        
    #endregion
    #region IDisposable Members
    public void Dispose()
    {
        this.messages.Clear();
        this.RoomID="";
        foreach(object key in RoomUsers.Keys)
        {
            this.RoomUsers[key.ToString()].Dispose ();
        }
    }
    #endregion
        
    #region Constructors
    public ChatRoom(string roomID) 
    {
        this.messages = new List<message />();
        this.RoomID=roomID;
    userChatRoomSessionTimeout = Int32.Parse
    (System.Configuration.ConfigurationManager.AppSettings
            ["UserChatRoomSessionTimeout"]);
        RoomUsers = new Dictionary<string,chatuser />
    (Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                    ["ChatRoomMaxUsers"]));
    }
    #endregion
    .
    .
    .
    }

ChatRoom 类中的主要方法是发送消息、加入房间和离开房间。

//ChatRoom.cs

#region Operations Join,Send,Leave
    /// Marks the user as inactive

    public void LeaveRoom(string userID)
    {
        //deactivate user 

        ChatUser user=this.GetUser(userID);
        if (user == null)
        return ;
        user.IsActive=false;
        user.LastSeen=DateTime.Now;
            this.RoomUsers.Remove(userID);

        //Add leaving message

        Message msg = new Message(user.UserName ,"",MsgType.Left);
        this.AddMsg(msg);
            
    if (IsEmpty())
    ChatEngine.DeleteRoom(this.RoomID);
    }

    /// Activates the user and adds a join message to the room

    /// <returns />All the messages sent in the room</returns />

    public string JoinRoom(string userID,string userName)
    {
        //activate user 

        ChatUser user=new ChatUser(userID,userName);
        user.IsActive=true;
        user.UserName=userName;
        user.LastSeen=DateTime.Now;
        if (!this.RoomUsers.ContainsKey(userID))
        {
            //Add join message

        Message msg=new Message(user.UserName ,"",MsgType.Join);
            this.AddMsg(msg);
            //Get all the messages to the user

            int lastMsgID;
            List<message /> previousMessages=
                this.GetMessagesSince(-1,out lastMsgID);
            user.LastMessageReceived=lastMsgID;
            //return the messages to the user

            string str=GenerateMessagesString(previousMessages);
            this.RoomUsers.Add(userID,user);
            return str;
        }
        return "";
    }

    /// Adds a message in the room

    /// <returns />All the messages sent from the other user from the last time 

    ///the user sent a message</returns />

    public string SendMessage(string strMsg,string senderID)
    {
        ChatUser user=this.GetUser(senderID);
        Message msg=new Message(user.UserName ,strMsg,MsgType.Msg);
        user.LastSeen=DateTime.Now;
        this.ExpireUsers(userChatRoomSessionTimeout);
        this.AddMsg(msg);
        int lastMsgID;
        List<message /> previousMsgs= this.GetMessagesSince
            ( user.LastMessageReceived,out lastMsgID);
        if (lastMsgID!=-1)
            user.LastMessageReceived=lastMsgID;
        string res=this.GenerateMessagesString(previousMsgs);
        return res;
    }

    /// Removes the users that hasn't sent any message during the 

    /// last window seconds 

    /// time in secondes

    public void ExpireUsers(int window) 
    {
        lock(this) 
        {
    foreach (object key in RoomUsers.Keys)
    {
            ChatUser usr = this.RoomUsers[key.ToString()];
                lock (usr)
                {
                  if (usr.LastSeen != System.DateTime.MinValue)
                  {
                    TimeSpan span = DateTime.Now - usr.LastSeen;
                    if (span.TotalSeconds > window && usr.IsActive != false)
                        {
                          this.LeaveRoom(usr.UserID);
                        }
                   }
                 }
                }
    }
    #endregion 
 

为了保持用户更新,以下函数检索房间中发送的所有消息,直到用户收到的最后一条消息为止。

//ChatRoom.cs

<summary />    /// Returns all the messages sent since the last message 

    /// the user received

    public string UpdateUser(string userID)
    {
        ChatUser user=this.GetUser(userID);
        user.LastSeen=DateTime.Now;
        this.ExpireUsers(userChatRoomSessionTimeout);
        int lastMsgID;
        List<message /> previousMsgs= this.GetMessagesSince
            ( user.LastMessageReceived,out lastMsgID);
        if (lastMsgID!=-1)
            user.LastMessageReceived=lastMsgID;
        string res=this.GenerateMessagesString(previousMsgs);
        return res;
    }
    /// Returns a list that contains all messages sent after the 

    ///             message with id=msgid

    /// The id of the message after which all 

    ///            the message will be retuned 

    /// the id of the last message returned

    public List<message /> GetMessagesSince(int msgid,out int lastMsgID) 
    {
        lock(messages) 
        {
        if ((messages.Count) <= (msgid+1))
            lastMsgID=-1;
        else
            lastMsgID=messages.Count-1;
        return messages.GetRange(msgid+1 , messages.Count - (msgid+1));
        }
    }
    

ChatEngine 类充当聊天室的容器。

//ChatEngine.cs

    public static class ChatEngine
    {
        #region Members
        private static Dictionary<string, /> Rooms = 
                new Dictionary<string, />
        (Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                            ["MaxChatRooms"]));
        private static int userChatRoomSessionTimeout = 
        Int32.Parse(System.Configuration.ConfigurationManager.AppSettings
                    ["UserChatRoomSessionTimeout"]);
        #endregion 
                
        #region Methods
    /// Cleans all the chat roomsDeletes the empty chat rooms

    public static void CleanChatRooms(object state)
        {
            lock (Rooms) 
            {
                foreach (object key in Rooms.Keys)
                {
                    ChatRoom room = Rooms[key.ToString()];
                    room.ExpireUsers(userChatRoomSessionTimeout);
                    if (room.IsEmpty())
                    {
                        room.Dispose();
                        Rooms.Remove(key.ToString());
                    }
                }
            }
        }

    /// Returns the chat room for this two users or create a new one 

    /// if nothing exists

    public static ChatRoom GetRoom(string roomID)
    {
        ChatRoom room=null;
            lock (Rooms)
            {
                if (Rooms.ContainsKey (roomID))
                    room = Rooms[roomID];
                else
                {
                    room = new ChatRoom(roomID);
                    Rooms.Add(roomID, room);
                }
            }
            return room;
        }

    /// Deletes the specified room

    public static void DeleteRoom(string roomID)
    {
            if (!Rooms.ContainsKey(roomID))
                return;
            lock (Rooms)
            {
                ChatRoom room = Rooms[roomID];
                room.Dispose();
                Rooms.Remove(roomID);
            }
        }
        #endregion
    }

当应用程序启动时,会设置一个应用程序级别的计时器,以定期清理空的聊天室。

//Global.asax

void Application_Start(object sender, EventArgs e) 
    {
        System.Threading.Timer ChatRoomsCleanerTimer = 
        new System.Threading.Timer(new TimerCallback
        (ChatEngine.CleanChatRooms), null, 1200000, 1200000);
    }

当您运行应用程序时,它会将您带到 default.aspx 页面,您将在其中被提示输入您将在聊天室中使用的用户名。之后,用户选择他将加入的聊天室。

聊天页面包含一个文本区域,用于显示聊天室中的所有消息,以及一个列表框,用于显示房间中的所有在线用户。用户在文本框中写入他的消息并按 Enter 或单击发送按钮以发送消息。一个文本框看起来像这样

Chat Room

客户端 AJAX

Chat.aspx 页面包含使用 AJAX 调用服务器上方法的 javascript。要在 AJAX 中异步调用服务器上的方法,您可以

  • 将此方法放在 Web 服务中,并将 ScriptService 属性应用于此 Web 服务
  • 或者将此方法放在 aspx 页面中作为静态公共方法,并将 WebMethod 属性应用于它

我在调用服务器端方法时使用了第二种方法。

所有 javascript 代码都放在 scripts.js 文件中,有四个从 javascript 发送到服务器的异步请求,前两个使用 javascript 计时器定期发送,updateUser 请求用于获取其他用户发送的所有消息,并使用这些消息更新文本区域。 updateMembers 请求用于获取房间中的所有在线成员,并更新成员列表框,以便从列表中删除离开的成员,并将新加入的成员添加到列表中。这两个请求的 javascript 代码如下所示

//scripts.js

        var msgTimer = "";
           var membersTimer = "";
        
        startTimers();
      
        function startTimers()
        {
            msgTimer = window.setInterval("updateUser()",3000);
            membersTimer = window.setInterval("updateMembers()",10000);
        } 
        function updateUser()
        {
            PageMethods.UpdateUser($get("hdnRoomID").value, UpdateMessages);
        }
        function updateMembers()
        {
            PageMethods.UpdateRoomMembers($get("hdnRoomID").value, 
                            UpdateMembersList);
        }
        function UpdateMessages(result)
        {
            $get("txt").value=$get("txt").value+result;
            $get("txt").doScroll();
        }
        function UpdateMembersList(result)
        {
           // alert(result);

            var users=result.split(",");
           // alert(users.length);

            var i=0;
                                
                $get("lstMembers").options.length=0;
                 var i=0;
                while (i < users.length)
                {
                    if (users[i]!="");
                    {
                        var op=new Option(users[i],users[i]);
                        $get("lstMembers").options[$get("lstMembers").
                    options.length]= op;
                    }
                    i+=1;
                }
               }

PageMethods 类由 AJAX 脚本管理器控件生成,并为所有服务器方法提供代理,以便您可以通过传递参数和传递脚本回调函数(当方法的结果从服务器到达时将调用的函数)的名称来调用该方法。

这两个请求的相应服务器端方法如下所示

//Chat.aspx.cs

  
    /// This function is called peridically called from the user to update 

    /// the messages

    [WebMethod]
    static public string UpdateUser(string roomID)
    {
        try
        {
            ChatRoom room = ChatEngine.GetRoom(roomID);
            if (room != null)
            {
                string res = "";
                if (room != null)
                {
                    res = room.UpdateUser(HttpContext.Current.Session
                    ["UserName"].ToString());
                }
                return res;
            }
        }
        catch (Exception ex)
        {

        }
        return "";
    }
    /// Returns a comma separated string containing the names of the users 

    /// currently online

    [WebMethod]
    static public string UpdateRoomMembers(string roomID)
    {
        try
        {
            ChatRoom room = ChatEngine.GetRoom(roomID);
            if (room != null)
            {
                IEnumerable<string /> users=room.GetRoomUsersNames ();
                string res="";

                foreach (string  s in users)
                {
                    res+=s+",";
                }
                return res;
            }
        }
        catch (Exception ex)
        {
        }
        return "";
    }

第三个请求在用户按下发送按钮时发送。此请求将用户消息发送到房间。

//scripts.js

    function button_clicked()
    {
        PageMethods.SendMessage($get("txtMsg").value,$get
        ("hdnRoomID").value, UpdateMessages, errorCallback);
        $get("txtMsg").value="";
        $get("txt").scrollIntoView("true");
    }
    
    function errorCallback(result)
    {
        alert("An error occurred while invoking the remote method: " 
        + result);
    }

此请求的相应服务器端方法是

//Chat.aspx.cs

<summary />    /// This function is called from the client script 

    [WebMethod]
    static public string SendMessage(string msg, string roomID)
    {
        try
        {
            ChatRoom room = ChatEngine.GetRoom(roomID);
            string res = "";
            if (room != null)
            {
                res = room.SendMessage
         (msg, HttpContext.Current.Session["UserName"].ToString());
            }
            return res;
        }
        catch (Exception ex)
        {

        }
        return "";
    }

最后一个请求在 <body> 元素的 onunload 事件期间调用。此方法通知服务器用户离开了房间。

//scripts.js

    function Leave()
    {
        stopTimers();
        PageMethods.LeaveRoom($get("hdnRoomID").value);
    }

此请求的相应服务器端方法是

//Chat.aspx.cs

<summary />    /// This function is called from the client when the user is about to 

    /// leave the room

    [WebMethod]
    static public string LeaveRoom(string roomID)
    {
        try
        {
            ChatRoom room = ChatEngine.GetRoom(roomID);
            if (room != null)
                room.LeaveRoom(HttpContext.Current.Session["UserName"].
                            ToString());
        }
        catch (Exception ex)
        {

        }
        return "";
    }
© . All rights reserved.