WebIM系列文章html
以前筆者發佈的雲翔在線軟件平臺中已經包含了一個功能相對比較齊全的WebIM,這個系列的文章就是介紹如何開發出功能相似的WebIM,在文章開始前,先介紹一下相關的技術:瀏覽器
1.Comet服務器
Comet 是一種新的 Web 應用架構。基於這種架構開發的應用中,服務器端會主動以異步的方式向客戶端程序推送數據,而不須要客戶端顯式的發出請求。Comet 架構很是適合事件驅動的 Web 應用,以及對交互性和實時性要求很強的應用,如股票交易行情分析、聊天室和 Web 版在線遊戲等。架構
在.NET要實現Comet就要用到IHttpAsyncHandler,在開始閱讀文章前,建議先了解一下IHttpAsyncHandler。異步
2.Lesktopasync
Lesktop是一款用於開發RIA網站的開源JS界面庫,Lesktop提供了一個功能強大的可視化開發工具幫助您快速的開發RIA網站。這個系列介紹的WebIM的前臺UI將使用Lesktop來開發。工具
接下來,將開始今天的主題,開發一個簡單的WebIM,這個WebIM將使用Comet技術,從而避免在客戶端和服務端輪詢,提升WebIM的性能(目前主要實現可以聊天,其餘功能會在之後不斷完善)。客戶端界面在這就不詳細介紹了,用Lesktop拖拖控件就能夠了,效果以下:性能
1.基本思路開發工具
Comet即是指服務器推技術。它的實現方式是在瀏覽器與服務器之間創建一個長鏈接,待得到消息以後當即返回。不然持續等待,直至超時。客戶端獲得消息或超時以後,又會當即創建另外一個長鏈接。Comet技術的最大優點,天然就是很高的即便性。在.NET中實現這種方式並不困難,用IHttpAsyncHandler便可。網站
接收消息的流程:
發送消息流程:
發送消息和添加監聽器將由一個類型爲MessageManagement對象來負責,
添加監聽器代碼以下:
/// <summary> /// 添加消息監聽器,若是查找到符合監聽器條件的消息,返回false,此時不會添加監聽器 /// 若是沒有查找到符合監聽器條件的消息,返回true,此時監聽器將被添加到m_Listeners中 /// </summary> public bool AddListener(String receiver, String sender, Nullable<DateTime> from, WebIM_AsyncResult asynResult) { MessageListener listener = new MessageListener(receiver, sender, from, asynResult); lock (m_Lock) { if (!m_Listeners.ContainsKey(receiver)) { m_Listeners.Add(receiver, new List<MessageListener>()); } List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>; //查找消息 List<Message> messages = Find(receiver, sender, from); if (messages.Count == 0) { //插入監聽器 listeners.Add(listener); } else { //發送消息 listener.Send(messages); } return messages.Count == 0; } }
發送消息代碼以下:
/// <summary> /// 插入新的消息,插入消息後將查詢m_Listeners中是否有符合條件的監聽器,如存在,同時將消息發送出去 /// </summary> public Message NewMessage(String receiver, String sender, DateTime createdTime, String content) { lock (m_Lock) { Message message = new Message(sender, receiver, content, createdTime, ++m_MaxKey); SQLiteCommand cmd = new SQLiteCommand( "insert into Message (Receiver,Sender,Content,CreatedTime,Key) values (?,?,?,?,?)", m_Conn ); cmd.Parameters.Add("Receiver", DbType.String).Value = message.Receiver; cmd.Parameters.Add("Sender", DbType.String).Value = message.Sender; cmd.Parameters.Add("Content", DbType.String).Value = message.Content; cmd.Parameters.Add("CreatedTime", DbType.DateTime).Value = message.CreatedTime; cmd.Parameters.Add("Key", DbType.Int64).Value = message.Key; cmd.ExecuteNonQuery(); List<Message> messages = new List<Message>(); messages.Add(message); if (m_Listeners.ContainsKey(receiver)) { List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>; List<MessageListener> removeListeners = new List<MessageListener>(); foreach (MessageListener listener in listeners) { if ((listener.Sender == "*" || String.Compare(listener.Sender, sender, true) == 0) && (listener.From == null || message.CreatedTime > listener.From)) { listener.Send(messages); removeListeners.Add(listener); System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(listener.Complete)); } } foreach (MessageListener listener in removeListeners) { //移除監聽器 listeners.Remove(listener); } } return message; } }
2.使用IHttpAsyncHandler實現Comet
IHttpAsyncHandler的介紹能夠查閱下msdn,如下是接收消息的源代碼,主要是重寫BeginProcessRequest和EndProcessRequest:
public class WebIM_ReceiveHandler : IHttpAsyncHandler { public WebIM_ReceiveHandler() { } HttpContext m_Context = null; IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) { m_Context = context; System.IO.Stream inputStream = context.Request.InputStream; Byte[] buffer = new Byte[inputStream.Length]; inputStream.Read(buffer, 0, (int)inputStream.Length); string content = context.Request.ContentEncoding.GetString(buffer); Hashtable data = Utility.ParseJson(content) as Hashtable; WebIM_AsyncResult asyncResult = new WebIM_AsyncResult(cb, extraData); Nullable<DateTime> from = data.ContainsKey("From") ? new Nullable<DateTime>((DateTime)data["From"]) : null; if (!MessageManagement.Instance.AddListener(data["Receiver"] as string, data["Sender"] as string, from, asyncResult)) { //已有消息,發送消息並結束連接 asyncResult.Complete(null); } return asyncResult; } void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) { //將消息發送到客戶端 WebIM_AsyncResult asyncResult = result as WebIM_AsyncResult; asyncResult.Send(m_Context); } void IHttpHandler.ProcessRequest(HttpContext context) { } bool IHttpHandler.IsReusable { get { return true; } } } public class WebIM_AsyncResult : IAsyncResult { AsyncCallback m_AsyncCallback = null; object m_Data = null; bool m_IsCompleted = false; public WebIM_AsyncResult(AsyncCallback callback, Object extraData) { m_Data = extraData; m_AsyncCallback = callback; } bool IAsyncResult.IsCompleted { get { return m_IsCompleted; } } bool IAsyncResult.CompletedSynchronously { get { return false; } } WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } } Object IAsyncResult.AsyncState { get { return m_Data; } } StringBuilder m_Cache = new StringBuilder(); public void Write(object content) { m_Cache.Append(content.ToString()); } public void Send(HttpContext context) { context.Response.Write(m_Cache.ToString()); } public void Complete(object data) { m_AsyncCallback(this); m_IsCompleted = true; } }
3.客戶端接收消息
客戶端接收消息並不複雜,只須要發送請求,返回後在發送另外一個請求便可,代碼以下:
function Receive() { var data = { Receiver: User, Sender: Peer, From: m_From }; function Receive_Error(ex) { alert(ex); m_ErrorCount++; if (m_ErrorCount < 5) { //發送下一個請求 setTimeout(Receive, 1000); } } function Receive_Callback(xml, text) { m_ErrorCount = 0; //將JSON轉成數據 var ret = System.ParseJson(text); //顯示消息 for (var i in ret.Messages) { m_MsgPanel.AddMessage(ret.Messages[i]); } if (ret.Messages.length > 0) { m_From = ret.Messages[ret.Messages.length - 1].CreatedTime; } //發送下一個請求 setTimeout(Receive, 50); } System.Post(Receive_Callback, Receive_Error, "recevie.aspx", System.RenderJson(data)); }
文章來自:http://www.cnblogs.com/lucc/archive/2010/04/24/1719397.html