[学习] 微型群聊天的实现讲解(一)

最近有点闲,于是漫无目的的在网上找资料学习。正好赶上WebQQ2.0发布,发现做的还蛮不错的,于是我也有了来模仿一下的想法。在博客园找了很久的资料,终于确定用COMET来作为聊天系统的主要连接技术,然后在加上一点jquery,本人把这个微型群聊天初步做了出来。本篇文章还参考了卢春城一步一步打造WebIM (额 其实COMET方面 主要还是他的代码)

1 用例图 

本人的画图水平有待提高,先将就着看吧。 图里演示了两个用户的登录情况。


2 实现方式 

 我们看到上图中有个Message Management的东西,他是处理所有信息的类,不管是发送消息处理,接收消息处理都是由他的操作指挥。而他直接管辖的就是listener,

 客户端以及服务器端的消息都会经过listener,他实现一个传达者的角色。

 接下来我主要说下send.aspx, receive.aspx,disconnect.aspx在这个聊天程序中的作用与基本实现。我在web.config中对这几个aspx进行了映射,实际的实现是通过几个

 IHttpAsyncHandler, IHttpHandler来做的。

 <httpHandlers>
      
<add path="recevie.aspx" verb="*" type="Will.ChatApp.WebCore.ConnectionHandler"/>
      
<add path="send.aspx" verb="*" type="Will.ChatApp.WebCore.SendMsgHandler"/>
      
<add path="disconnect.aspx" verb="*" type="Will.ChatApp.WebCore.DisconnectHandler"/>
 
</httpHandlers>

 a) receive.aspx (ConnectionHandler)

     这个httpHandler是负责实现COMET连接的,他是一个IHttpAsyncHandler。主要方法有BeginProcessRequest,EndProcessRequest。我在BeginProcessRequest中接受客户端的请求,只要ConnectionAsyncResult的IsComplete属性不为true,这个请求将永远保持下去,这样就实现了我们想要的客户端http 长连接。这里我们做的是个群聊天,所以当任何一个客户端发来请求,都需要通知其他客户有消息到来。 

    于是我们就需要把这个 ConnectionAsyncResult保存起来,当有其他客户发送消息,再及时把这个ConnectionAsyncResult.IsComplete设为true,释放连接通知客户端。而这正是我们需要listener的地方。

    代码

public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            _context 
= context;

            ConnectionAsyncResult asyncResult 
= new ConnectionAsyncResult(cb, extraData);

            
string receiver = "*";
            
string sender = context.Request["Sender"];
            
string from = context.Request["From"];
            
string type = context.Request["Type"];

            if (!MessageManagement.Instance.AddListener(receiver, sender, asyncResult, type))
            {
                
//已有消息,发送消息并结束链接
                asyncResult.Complete();
            }

            
return asyncResult;
        }

   下面我高亮了几个方法。第一亮点是如果当用户是新登录,于是会将用户添加到用户列表,并放回一个新用户标记。第二个亮点是将这个用户的活动时间更新,后面我们判断用户是否掉线会用到。第三个亮点是新起一个线程,如果10s内用户没有接收到任何消息或者操作,这个连接将会被关闭。第四个亮点判断是新用户登陆后,就会把更新所有客户端的用户列表并通过监听器发送出去。

            

代码
        /// <summary>
        
/// 添加消息监听器,如果查找到符合监听器条件的消息,返回false,此时不会添加监听器
        
/// 如果没有查找到符合监听器条件的消息,返回true,此时监听器将被添加到m_Listeners中
        
/// 如果是已连接用户 就更新该用户的活动时间
        
/// </summary>
        public bool AddListener(String receiver, String sender, ConnectionAsyncResult asynResult, String type)
        {
            MessageListener listener = new MessageListener(receiver, sender, 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>;

             1   bool flag = IsNewUserLogin(listeners, sender, type);

                
//if reconnect, update the status as alive
             2   onlineUserList.OnlineUserList.ForEach(p =>
                {
                    if (p.UserName == listener.Sender)
                    {
                        p.Status = ConnectionStatus.Alive;
                        p.UpdateTime = DateTime.Now;
                    }
                });

                //查找消息
                ChatMessage messages = Find(receiver, sender);

                
if (messages.ChatMessageList.Count == 0)
                {
                    
//插入监听器
                    listeners.Add(listener);
             3       new Timer(TimerCallBack, listener, 100000);
                }
                
else
                {
                    
//发送消息
                    listener.Send(messages);
                }

                
//通知所有人上线消息
                if (flag)
                {
            4        SpreadOtherListener(listeners);
                }

                
return messages.ChatMessageList.Count == 0;
            }
        }


 b) send.aspx(SendMsgHandler)

 这个比较简单,只是单纯的处理用户发送的消息。当Message Management 接到消息后,会通知所有的listener有消息到。

 代码

 public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType 
= "text/plain";

            
string receiver = context.Request["Receiver"];
            
string sender = context.Request["Sender"];
            
string message = context.Request["Message"];

            SoleChatMessage result 
= new SoleChatMessage();
            result.ChatContent 
= new MessageContext() { MessageContent = message };
            result.ChatTime 
= DateTime.Now;
            result.Sender 
= sender;
            result.SenderIP 
= context.Request.UserHostAddress;
            result.Receiver 
= receiver;
            
            MessageManagement.Instance.NewMessage(result);
        }

 b) disconnect.aspx(DisconnectHandler.cs)

 这个httpHandler主要是处理当用户点击关闭窗口按钮时做的操作。 此时Message Management接到消息后会遍历所有listener,发现时当前客户端发来的请求,则关闭该客户端的连接,移除listener。 而listener的查找标识就是登录客户端的名称,这个还是有点不大安全的。

 代码

 public void ProcessRequest(HttpContext context)
        {
            
string sender =context.Request["Sender"];
            
string recevier =context.Request["Receiver"];

            MessageManagement.Instance.DisposeDisconnectListener(recevier,sender);
        }
代码
/// <summary>
        
/// 当用户点击关闭窗口按钮后触发 把用户从用用列表中踢出 并且释放监听器
        
/// </summary>
        
/// <param name="recevier"></param>
        
/// <param name="sender"></param>
        public void DisposeDisconnectListener(string recevier, string sender)
        {
            List
<MessageListener> listeners = m_Listeners[recevier] as List<MessageListener>;
            List
<MessageListener> removeListeners = new List<MessageListener>();

            var user 
= onlineUserList.OnlineUserList.Find(p => p.UserName == sender);
            
if (user != null)
            {
                onlineUserList.OnlineUserList.Remove(user);
            }

            
foreach (MessageListener listener in listeners)
            {
                removeListeners.Add(listener);
                
if (String.Compare(listener.Sender, sender, true== 0)
                {
                    System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(listener.Complete));
                }
                
else
                {
                    listener.Send(onlineUserList);
                    System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(listener.Complete));
                }
            }

            
foreach (MessageListener listener in removeListeners)
            {
                
//移除监听器
                listeners.Remove(listener);
            }
        }

3 问题

这次主要讲解下,大致整个流程,其实还有很多没提到。比如前台的实现,还有前台的安全,后台的安全验证等等。 我的程序还在修改,有些东西会后期加上去的。

最后,希望我不要写的烂尾 谢谢

4 代码提供 ChatApp.rar

原文地址:https://www.cnblogs.com/baweiji/p/1832347.html