惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

博客园 - 卢春城

Lesktop开源IM移动端:接入LayIM移动端UI 开源企业即时通讯和在线客服 开源企业即时通讯和在线客服 在SQL Server中对视图进行增删改 WebBrowser介绍——Javascript与C++互操作 开源WebOS 一步一步打造WebIM(4)——Comet的特殊之处 网页信息抓取 一步一步打造WebIM(3)——性能测试 在线CHM阅读器(2)——文件提取及关键文件解析 一步一步打造WebIM(2)——消息缓存 在线CHM阅读器(1)——CHM文件格式概述 谈谈网站设计时图片的使用 如何开发HTML编辑器 Variable控件[更新至1.3]--在客户端和服务器之间传送变量 公交车路线查询系统后台数据库设计--换乘算法改进与优化 公交车路线查询系统后台数据库设计 公交车路线查询系统后台数据库设计--引入步行路线 远程控制程序
一步一步打造WebIM(1)
卢春城 · 2010-04-24 · via 博客园 - 卢春城

WebIM系列文章

笔者开发的WebOS中已经包含了一个功能相对比较齐全的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));
}

WebIM源代码(注意:开发时起始页为WebIM_Dev.htm,直接打开起始页为WebIM.htm)

一个简单的WebIM就先介绍到这里,如果您有任何问题,可以通过WebIM与我联系。