一步一步打造WebIM(1)

来源:互联网 发布:杭州淘宝美工招聘 编辑:程序博客网 时间:2024/04/30 06:24

转自:博客园,卢春城专栏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));}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 医保卡姓名弄错了怎么办 走工伤和走社保怎么办 公司在朝阳社保在海淀怎么办 公司没缴纳个税怎么办 报个税工资报少了怎么办 医院预约单丢了怎么办 肛瘘手术后太疼怎么办 低位保肛手术后吻合口瘘怎么办 做完痔疮手术后大便困难怎么办 20岁长痔疮了怎么办 孕妇痔疮痒的难受怎么办 痔疮术后伤口不愈合怎么办 剖腹产液化伤口长的慢怎么办 内痔斑痕怎么办了能消化 油条面和稀了怎么办 解脲支原体感染怀孕怎么办 怀孕了检查出解脲支原体感染怎么办 大便是黑色的要怎么办 大人直肠给药不好意思怎么办 肛周脓肿长肉芽怎么办 二岁宝宝有直肠息肉怎么办 87岁老人得直肠息肉怎么办 做b超查出囊肿怎么办 解大便时肛门痒怎么办 生完孩子肛门痒怎么办 把达克宁软膏摸到肛门怎么办 痔疮手术后肛门痒怎么办 便秘拉完屁股疼怎么办 屁股拉屎拉破了怎么办 6岁小朋友屁眼痒怎么办 孩子屁屁偶尔痒怎么办 1岁半宝宝屁眼痒怎么办 3岁宝宝肛门痒怎么办 得痔疮发烧了怎么办啊 痔疮手术后肚子胀气怎么办 乙肝引起的发烧头痛怎么办 肠癌手术后吃了会胃疼怎么办 来月经肛门坠痛怎么办 总想排便还有血怎么办 闻了别人的口臭怎么办 有内痔肛门经常不舒服怎么办