WebIM组件设计
其实对于一个WebIM来说,我们一般都几种方式来实现
1.服务器拉送方式:就是客户端主动定时的去服务器端获取信息,一般我们可以通过 Ajax控件+定时器 来实现,优点:实现简单,缺点:服务器承受的压力重
2.服务器推送方式:就是服务器主动将获取到的信息,发送给客户端,Asp.net我们可以通过 IHttpAsyncHandler这个接口来实现,优点:服务器承受的压力小 缺点:服务器 和客户端同时配合编写(js)
3.Flash Socket方式:通过在网页中嵌入Flash,编写Socket程序 通过AS 3.0语言,Socket编程,优点:并发量大,服务器压力小,缺点:浏览器中要支持Flash,加载Flash
下面我实现的是第二种:
它实现的原理很简单,浏览器向服务器发送一个异步请求,服务器接到请求之后,去内存中查找信息,如果有,就直接处理该请求,如果没有,我们可以把该请求暂存到服务器一段时间,如果在该时间段内,有符合自己信息的到来,我们就直接处理,否则我们要丢掉该请求(其实请求丢掉之后,如果客户端任然在线,客户端会重新在次发送请求,就像是心跳包)
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Web;
namespace DNCCFrameWork.Chat
{
/// <summary>
/// Chat聊天异步处理程序
/// </summary>
public class ChatAsyncHandler : IHttpAsyncHandler
{
//接受客户端请求
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
{
System.IO.Stream stream = context.Request.InputStream;
byte[] b = ReadDate(stream);
string str = context.Server.UrlDecode(context.Request.ContentEncoding.GetString(b));
Hashtable ht = SplitRequestPara(str);
string type = ht["type"] as string;
string message = ht["content"] as string;
string sendUser = ht["sendusers"] as string;
string receiveUser = ht["receiveusers"] as string;
IChatMessage chatMessage = new ChatMessage();
chatMessage.Type = type;
chatMessage.Content = message;
chatMessage.SendUser = sendUser;
chatMessage.ReceiveUser = receiveUser;
ChatAsyncResult chatAsyncResult = new ChatAsyncResult(context, cb, extraData, chatMessage);
IChat chat = new ChatDefault();
chat.IM(chatAsyncResult);
return chatAsyncResult;
}
#region 读流中的全部数据
private byte[] ReadDate(System.IO.Stream stream)
{
byte[] buff = new byte[stream.Length];
int curOffset = 0;
int totalCount = 0;
int readCount = 0;
int size = buff.Length;
while (totalCount < size)
{
int exceptSize = size - totalCount;
readCount = stream.Read(buff, curOffset, exceptSize);
curOffset += readCount;
totalCount += readCount;
}
return buff;
}
#endregion
#region 分离请求数据
private Hashtable SplitRequestPara(string str)
{
Hashtable ht = new Hashtable();
string[] str1 = str.Split(new char[] { '&' });
foreach (string temp in str1)
{
string[] str2 = temp.Split(new char[] { '=' });
if (str2.Length == 2)
{
if (!ht.ContainsKey(str2[0].ToLower().ToString()))
{
ht.Add(str2[0].ToLower().ToString(), str2[1].ToString());
}
}
}
return ht;
}
#endregion
public void EndProcessRequest(IAsyncResult result)
{
}
#region IHttpHandler接口
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
}
#endregion
#region 请求检测
private bool RequestCheck(IChatMessage cm)
{
bool status = true;
if (string.IsNullOrEmpty(cm.Type))
{
status = false;
}
else
{
try
{
cm.Type = Common.Security.CryptoTextBase.ProcessText(cm.Type, false);
}
catch
{
status = false;
}
}
if (string.IsNullOrEmpty(cm.SendUser))
{
status = false;
}
if (string.IsNullOrEmpty(cm.ReceiveUser))
{
status = false;
}
return status;
}
#endregion
}
}
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace DNCCFrameWork.Chat
{
public class ChatMessageDispatcherDefault : IChatMessageDispatcher
{
private IChatMessageSendStrategy m_chatMessageSendStrategy = new ChatMessageSendStrategyDefault();
private IChatMessageHook m_chatMessageHook = new ChatMessageHookDefault();
private IChatMessageSpy m_chatMessageSpy = new ChatMessageSpyDefault();
private IChatMessageDeal m_chatMessageDeal;
private IChatMessageManager m_chatMessageManager = new ChatMessageManagerDefault();
private IChatRequestManager m_chatRequestManager = new ChatRequestManagerDefault();
private static object o = new object();
public void Dispatcher(ChatAsyncResult chatAsyncResult)
{
ChatAsyncResult car = Receive(chatAsyncResult);//接受请求
m_chatMessageDeal = ChatMessageDealFactory.GetChatMessageDeal(car);//获取请求对应的处理器
if (m_chatMessageDeal != null)
{
m_chatMessageDeal.Deal(car);//处理器处理消息,保存消息,或保存请求
lock (o)
{
DispatcherSend();//发送消息
}
}
}
public void DispatcherSend()
{
int count = m_chatRequestManager.Count();
for (int i = 0; i < count; i++)
{
IChatRequest chatRequest = m_chatRequestManager.Pop();
if (chatRequest != null)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("SendUser", chatRequest.ChatAsyncResult.ChatMessage.SendUser);
dic.Add("ReceiveUser", chatRequest.ChatAsyncResult.ChatMessage.ReceiveUser);
dic.Add("Type", chatRequest.ChatAsyncResult.ChatMessage.Type);
IList lt = m_chatMessageManager.Find(dic);
if (lt.Count <= 0)
{
TimeSpan ts = DateTime.Now - chatRequest.ChatAsyncResult.ChatMessage.CreateDate;
if (ts.TotalSeconds < 600)//请求超时系统丢弃请求
{
m_chatRequestManager.Push(chatRequest);
}
}
else
{
IList temp = new ArrayList(); ;
foreach (IChatMessage chatMessage in lt)
{
IChatMessage cm = Send(chatMessage);
temp.Add(cm);//发送消息前
}
StringBuilder sb = new StringBuilder();
for (int j = 0; j < temp.Count; j++)
{
IChatMessage cha = temp[j] as IChatMessage;
sb.Append(cha.ToString()).Append("<br/>");
}
chatRequest.ChatAsyncResult.ChatMessage.Content = sb.ToString();
m_chatMessageSendStrategy.Send(null, chatRequest);
}
}
}
}
public IChatMessage Send(IChatMessage chatAsyncResult)
{
IChatMessage car = m_chatMessageHook.Send(chatAsyncResult);
car = m_chatMessageSpy.Send(car);
return car;
}
public ChatAsyncResult Receive(ChatAsyncResult chatAsyncResult)
{
ChatAsyncResult car = m_chatMessageHook.Receive(chatAsyncResult);
car = m_chatMessageSpy.Receive(car);
return car;
}
}
}
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Configuration;
namespace DNCCFrameWork.Chat
{
public class ChatMessageStoreageMemory : IChatMessageStorage
{
private static Dictionary<string, LinkedList<IChatMessage>> m_SendUserReceiveChatMessage = new Dictionary<string, LinkedList<IChatMessage>>();
private static ChatMessageStoreageDataBase db = new ChatMessageStoreageDataBase();
private static Dictionary<string, object> m_Lock = new Dictionary<string, object>();
private static int m_ChatGroupALiveTime = 5;
private static int m_ChatMessageGC = 5;
private static DateTime m_LastChatMessageGC = DateTime.Now;
private static System.Timers.Timer GC = new System.Timers.Timer(m_ChatMessageGC * 60 * 1000);
static ChatMessageStoreageMemory()
{
IList lt = db.Find();//获取数据库中为发送信息
foreach (IChatMessage chatMessage in lt)
{
BackChatMessage(chatMessage);
}
int.TryParse(ConfigurationManager.AppSettings["ChatGroupALiveTime"], out m_ChatGroupALiveTime);
int.TryParse(ConfigurationManager.AppSettings["ChatMessageGC"], out m_ChatMessageGC);
GC.Interval = m_ChatMessageGC * 60 * 1000;
GC.Elapsed += new System.Timers.ElapsedEventHandler(GC_Elapsed);
GC.Enabled = true;
GC.Start();
}
static void GC_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ChatMessageGC();
}
private static void BackChatMessage(IChatMessage chatMessage)
{
string key = "";
string key2 = "";
if (chatMessage.Type.Equals(ChatType.oneTonoeSend))
{
key = chatMessage.SendUser + "<-->" + chatMessage.ReceiveUser;
key2 = chatMessage.ReceiveUser + "<-->" + chatMessage.SendUser;
}
if (chatMessage.Type.Equals(ChatType.manyTomanySend))
{
key = key2 = chatMessage.ReceiveUser;
}
if (!m_SendUserReceiveChatMessage.ContainsKey(key) || !m_SendUserReceiveChatMessage.ContainsKey(key2))
{
lock (m_SendUserReceiveChatMessage)
{
if (!m_SendUserReceiveChatMessage.ContainsKey(key) && !m_SendUserReceiveChatMessage.ContainsKey(key2))
{
m_SendUserReceiveChatMessage.Add(key, new LinkedList<IChatMessage>());
m_Lock.Add(key, new object());
}
}
}
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
lock (m_Lock[key])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
if (m_SendUserReceiveChatMessage[key].Count >= 1)
{
bool status = false;
IChatMessage temp = null;
foreach (IChatMessage cm in m_SendUserReceiveChatMessage[key])
{
if (cm.CreateDate > chatMessage.CreateDate)
{
temp = cm;
status = true; break;
}
}
if (status)
{
m_SendUserReceiveChatMessage[key].AddBefore(m_SendUserReceiveChatMessage[key].Find(temp), chatMessage);
}
if (!status)
{
m_SendUserReceiveChatMessage[key].AddLast(chatMessage);
}
}
else
{
m_SendUserReceiveChatMessage[key].AddFirst(chatMessage);
}
}
}
}
else
{
lock (m_Lock[key2])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key2))
{
if (m_SendUserReceiveChatMessage[key2].Count >= 1)
{
bool status = false;
IChatMessage temp = null;
foreach (IChatMessage cm in m_SendUserReceiveChatMessage[key2])
{
if (cm.CreateDate > chatMessage.CreateDate)
{
temp = cm;
status = true;
break;
}
}
if (status)
{
m_SendUserReceiveChatMessage[key2].AddBefore(m_SendUserReceiveChatMessage[key2].Find(temp), chatMessage);
}
if (!status)
{
m_SendUserReceiveChatMessage[key2].AddLast(chatMessage);
}
}
else
{
m_SendUserReceiveChatMessage[key2].AddFirst(chatMessage);
}
}
}
}
}
public void Add(IChatMessage chatMessage)
{
string key = "";
string key2 = "";
if (chatMessage.Type.Equals(ChatType.oneTonoeSend))
{
key = chatMessage.SendUser + "<-->" + chatMessage.ReceiveUser;
key2 = chatMessage.ReceiveUser + "<-->" + chatMessage.SendUser;
}
if (chatMessage.Type.Equals(ChatType.manyTomanySend))
{
key = key2 = chatMessage.ReceiveUser;
}
if (!m_SendUserReceiveChatMessage.ContainsKey(key) || !m_SendUserReceiveChatMessage.ContainsKey(key2))
{
lock (m_SendUserReceiveChatMessage)
{
if (!m_SendUserReceiveChatMessage.ContainsKey(key) && !m_SendUserReceiveChatMessage.ContainsKey(key2))
{
m_SendUserReceiveChatMessage.Add(key, new LinkedList<IChatMessage>());
m_Lock.Add(key, new object());
}
}
}
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
lock (m_Lock[key])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
if (m_SendUserReceiveChatMessage[key].Count >= 1)
{
bool status = false;
IChatMessage temp = null;
foreach (IChatMessage cm in m_SendUserReceiveChatMessage[key])
{
if (cm.CreateDate > chatMessage.CreateDate)
{
temp = cm;
status = true; break;
}
}
if (status)
{
m_SendUserReceiveChatMessage[key].AddBefore(m_SendUserReceiveChatMessage[key].Find(temp), chatMessage);
}
if (!status)
{
m_SendUserReceiveChatMessage[key].AddLast(chatMessage);
}
}
else
{
m_SendUserReceiveChatMessage[key].AddFirst(chatMessage);
}
cb c = new cb(db.Add);
c.BeginInvoke(chatMessage, null, null);//异步写入数据库
}
}
}
else
{
lock (m_Lock[key2])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key2))
{
if (m_SendUserReceiveChatMessage[key2].Count >= 1)
{
bool status = false;
IChatMessage temp = null;
foreach (IChatMessage cm in m_SendUserReceiveChatMessage[key2])
{
if (cm.CreateDate > chatMessage.CreateDate)
{
temp = cm;
status = true;
break;
}
}
if (status)
{
m_SendUserReceiveChatMessage[key2].AddBefore(m_SendUserReceiveChatMessage[key2].Find(temp), chatMessage);
}
if (!status)
{
m_SendUserReceiveChatMessage[key2].AddLast(chatMessage);
}
}
else
{
m_SendUserReceiveChatMessage[key2].AddFirst(chatMessage);
}
cb c = new cb(db.Add);
c.BeginInvoke(chatMessage, null, null);//写入数据库
}
}
}
}
public IList Find(Dictionary<string, string> dic)
{
string key = "";
if (dic["Type"].Equals(ChatType.oneTooneReceive))
{
key = dic["SendUser"] + "<-->" + dic["ReceiveUser"];
}
if (dic["Type"].Equals(ChatType.manyTomanyReceive))
{
key = dic["ReceiveUser"];
}
IList lt = new ArrayList();
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
lock (m_Lock[key])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
foreach (IChatMessage chatMessage in m_SendUserReceiveChatMessage[key])
{
if (!chatMessage.IsSend(dic["SendUser"]) && !chatMessage.IsOver)
{
chatMessage.SendUserList.Add(dic["SendUser"]);
chatMessage.ReadDateList.Add(DateTime.Now.ToString());
ChatMessageOverCheck(chatMessage);
cb c = new cb(db.Update);
c.BeginInvoke(chatMessage.Clone(), null, null);//异步更新数据库
lt.Add(chatMessage);
}
}
}
}
}
else
{
if (dic["Type"].Equals(ChatType.oneTooneReceive))
{
key = dic["ReceiveUser"] + "<-->" + dic["SendUser"];
}
if (dic["Type"].Equals(ChatType.manyTomanyReceive))
{
key = dic["ReceiveUser"];
}
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
lock (m_Lock[key])
{
if (m_SendUserReceiveChatMessage.ContainsKey(key))
{
foreach (IChatMessage chatMessage in m_SendUserReceiveChatMessage[key])
{
if (!chatMessage.IsSend(dic["SendUser"]) && !chatMessage.IsOver)
{
chatMessage.SendUserList.Add(dic["SendUser"]);
chatMessage.ReadDateList.Add(DateTime.Now.ToString());
ChatMessageOverCheck(chatMessage);
cb c = new cb(db.Update);
c.BeginInvoke(chatMessage.Clone(), null, null);//异步更新数据库
lt.Add(chatMessage);
}
}
}
}
}
}
return lt;
}
public void Clear(Dictionary<string, string> dic)
{
}
public void Clear(IList lt)
{
}
#region 定时内存清理
private static void ChatMessageGC()
{
foreach (KeyValuePair<string, LinkedList<IChatMessage>> kvp in m_SendUserReceiveChatMessage)
{
if (kvp.Value.Count > 0)
{
lock (m_Lock[kvp.Key])
{
if (kvp.Value.Count > 0)
{
IList lt = new ArrayList();
foreach (IChatMessage cm in kvp.Value)
{
if (!cm.IsOver)
{
ChatMessageOverCheck(cm);
}
if (cm.IsOver)
{
lt.Add(cm);
}
}
foreach (IChatMessage cm in lt)
{
kvp.Value.Remove(cm);
cb b = new cb(db.Add2);
b.BeginInvoke(cm.Clone(), null, null);//进入历史数据库
}
}
}
}
}
}
#endregion
#region 消息完成结束检测
private static void ChatMessageOverCheck(IChatMessage cm)
{
if (cm.Type.Equals(ChatType.oneTonoeSend) && cm.SendUserList.Count >= 2)
{
cm.OverDate = DateTime.Now;
cm.IsOver = true;
}
if (cm.Type.Equals(ChatType.manyTomanySend))
{
TimeSpan ts = DateTime.Now - cm.CreateDate;
if (ts.TotalMinutes > m_ChatGroupALiveTime)
{
cm.OverDate = DateTime.Now;
cm.IsOver = true;
}
}
}
#endregion
}
public delegate void cb(IChatMessage chatMessage);
}
所有的消息都先存储在内存中,然后异步更新到数据库中(相当于内存快照,一旦应用程序重启,没有被处理完过的消息,将重新加载到内存中),一旦消息被处理过(一对一聊天,双方都接受到)则消息标记为已处理过,消息就会从内存中清除,同时被异步更新到历史库表中
JS部分:
<script src="../Js/jquery-1.4.3.js" type="text/javascript" language="javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
function send()
{
var str=$.trim($("#ctl00_ContentPlaceHolder1_tbMSGInput").val());
var sendUser = $.trim('<% = MyUid %>');
var receiveUser = $.trim('<% = ToUID %>');
if(sendUser.length<1&&receive.length<1)
{
return;
}
if(str.length>1)
{
$.ajax({
type: "post",
url: "IM.aspx",
timeout:30000,
data:{ content: str, type: "0", sendUsers: sendUser, receiveUsers: receiveUser } ,
dataType:"html",
success: function (data, status)
{
if(data!="发送成功")
{
var result = $("#ctl00_ContentPlaceHolder1_lbChatShow");
result.html(result.html()+data+"<br/>");
}
else
{
$("#ctl00_ContentPlaceHolder1_tbMSGInput").val("");
}
},
error: function(XMLHttpRequest, textStatus, errorThrown)
{
var result = $("#ctl00_ContentPlaceHolder1_lbChatShow");
result.html(result.html()+textStatus+"<br/>");
}
});
$("#ctl00_ContentPlaceHolder1_tbMSGInput").val("");
}
}
function wait()
{
var sendUser = $.trim('<% = MyUid %>');
var receiveUser = $.trim('<% = ToUID %>');
if(sendUser.length<1&&receive.length<1)
{
return;
}
$.ajax({
type: "post",
url: "IM.aspx",
timeout:30000,
data:{ content: "", type: "1", sendUsers: sendUser, receiveUsers: receiveUser } ,
dataType:"html",
success:function (data, status)
{
var result = $("#ctl00_ContentPlaceHolder1_lbChatShow");
result.html(result.html()+data);
var outerHeight = $("#ctl00_ContentPlaceHolder1_lbChatShow").outerHeight();
var innerHeight = $("#div_Message").innerHeight();
var scrollTop = outerHeight - innerHeight + 20;
$("#div_Message").scrollTop(scrollTop);
wait();
},
error: function(XMLHttpRequest, textStatus, errorThrown)
{
wait();
}
});
};
//初始化连接
wait();
$("#btnSend").click(function () { send(); });
$("#ctl00_ContentPlaceHolder1_tbMSGInput").keypress(function (event) {
if (event.keyCode == 13) {
send();
}
});
});
</script>
部署方式:
在Web.config httpHandlers中加入即可
<add path="IM.aspx" type="DNCCFrameWork.Chat.ChatAsyncHandler" verb="POST,Get"/>