一步一步打造WebIM(1)

轉自:博客園,盧春城專欄http://www.cnblogs.com/lucc/archive/2010/04/27/1722470.html


之前筆者發佈的雲翔在線軟件平臺中已經包含了一個功能相對比較齊全的WebIM,這個系列的文章就是介紹如何開發出功能類似的WebIM,在文章開始前,先介紹一下相關的技術:

1.Comet

Comet 是一種新的 Web 應用架構。基於這種架構開發的應用中,服務器端會主動以異步的方式向客戶端程序推送數據,而不需要客戶端顯式的發出請求。Comet 架構非常適合事件驅動的 Web 應用,以及對交互性和實時性要求很強的應用,如股票交易行情分析、聊天室和 Web 版在線遊戲等。

在.NET要實現Comet就要用到IHttpAsyncHandler,在開始閱讀文章前,建議先了解一下IHttpAsyncHandler

2.Lesktop

Lesktop是一款用於開發RIA網站的開源JS界面庫,Lesktop提供了一個功能強大的可視化開發工具幫助您快速的開發RIA網站。這個系列介紹的WebIM的前臺UI將使用Lesktop來開發。

 

接下來,將開始今天的主題,開發一個簡單的WebIM,這個WebIM將使用Comet技術,從而避免在客戶端和服務端輪詢,提高WebIM的性能(目前主要實現能夠聊天,其他功能會在以後不斷完善)。客戶端界面在這就不詳細介紹了,用Lesktop拖拖控件就可以了,效果如下:

1

1.基本思路

Comet便是指服務器推技術。它的實現方式是在瀏覽器與服務器之間建立一個長連接,待獲得消息之後立即返回。否則持續等待,直至超時。客戶端得到消息或超時之後,又會立即建立另一個長連接。Comet技術的最大優勢,自然就是很高的即使性。在.NET中實現這種方式並不困難,用IHttpAsyncHandler即可。

接收消息的流程:

clip_image001

發送消息流程:

clip_image001[8]

發送消息和添加監聽器將由一個類型爲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));
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章