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






4.82/5 (41投票s)
2007 年 2 月 28 日
4分钟阅读

451610

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

引言
构建交互式聊天室需要保持用户了解其他用户的消息和状态。使用 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
}
此类表示将加入聊天室的用户。我们感兴趣的属性是 IsActive
、LastSeen
和 LastMessageReceived
。请注意,在您的应用程序中,您可能正在使用 ASP.NET 成员资格提供程序。您可以轻松地将其与我们的聊天应用程序一起使用。您会发现 MembershipUser
类包含两个属性 IsOnline
和 LastActivityDate
,可以分别用于替换属性 IsActive
和 LastSeen
。
聊天室中出现的每条消息都由以下类表示
//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 或单击发送按钮以发送消息。一个文本框看起来像这样
客户端 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 "";
}