zoukankan      html  css  js  c++  java
  • [学习] 微型群聊天的实现讲解(一)

    最近有点闲,于是漫无目的的在网上找资料学习。正好赶上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

  • 相关阅读:
    .TTableRegionMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    Cannot resolve com.google.zxing:javase:unknown
    为什么需要权限管理?
    spring boot 上传文件出错:org.springframework.web.multipart.MultipartException: Could not parse multipart s
    如何在windows上设置文件的默认打开方式
    如何将vscode设置为中文
    使用hutool工具类进行简单的excel导入和导出的工具类
    上传照片到本地服务器上(亲测有效)
    Centos 防火墙
    VirtualBox 安装增强工具
  • 原文地址:https://www.cnblogs.com/baweiji/p/1832347.html
Copyright © 2011-2022 走看看