ESFramework介绍之(27)-- 支持OverdueMessage

   (本文适用于ESFramework V0.2+)
     QQ上,你给好友发消息,如果对方不在线,则服务器会把这个消息持久化存起来,等好友下次上线时,再转发给他。像这样的消息在ESFramework中称为OverdueMessage。显然,MSN没有支持OverdueMessage。 当然了,ESFramework中的OverdueMessage不仅仅是文字消息,而是可以为任何类型的消息,比如,音频、视频、文件等等。

    ESFramework中对所有P2P的消息的处理都是由P2PMessageDealer完成的,当P2PMessageDealer转发一个消息时,发现目标用户不在线,就会将这个消息交给一个称为IOverdueMessageHandler的组件处理,IOverdueMessageHandler组件可以持久化存储这个消息,也可以直接将其保存在内存中,具体采用哪种方式,取决于你的应用。
    IOverdueMessageHandler组件有如下职责:
(1)保存(或持久化)存储OverdueMessage
(2)提取指定目标用户的所有OverdueMessage
(3)当OverdueMessage成功发送给目标用户后,改变OverdueMessage的状态或删除这些OverdueMessage。

    基于此,IOverdueMessageHandler接口设计如下:

    public interface IOverdueMessageHandler
    {
        bool  Enabled{set ;}
        void  HandleOverdueP2PMessage(NetMessage revMsg) ; 
        IList PickupOverdueP2PMessage(string destUserID) ; //列表中为NetMessage元素
        void  SetOverdueMessageSended(string destUserID) ;
    }

    对应的类图:
    
    IOverdueMessageHandler由应用实现,因为只有你的应用需求才能决定处理OverdueMessage的物理方式。P2PMessageDealer的实现中引入了IOverdueMessageHandler,如下:

            int result = this.toClientSender.HookAndSendMessage(reqMsg.Header.DestUserID ,reqMsg) ;
            if(result != DataSendResult.Succeed) //处理没有发送成功的消息
            {
                if(this.overdueMessageHandler != null)
                {
                    this.overdueMessageHandler.HandleOverdueP2PMessage(reqMsg) ;
                }
            }      
            //

    我们需要另外一个组件来负责发送OverdueMessage给目标用户,那就是OverdueMessageSender,它可以让你指定在多久之后发送OverdueMessage给指定的用户,如果没有指定这个时间间隔,就立即发送。
   

OverdueMessageSender
    public class OverdueMessageSender
    {
        #region property
        #region ToClientSender
        private IToClientSender toClientSender = null ; 
        public  IToClientSender ToClientSender
        {
            set
            {
                this.toClientSender = value ;
            }
        }
        #endregion

        #region OverdueMessageHandler
        private IOverdueMessageHandler overdueMessageHandler = null ; 
        public  IOverdueMessageHandler OverdueMessageHandler
        {
            set
            {
                this.overdueMessageHandler = value ;
            }
        }
        #endregion    

        #region SendDelay
        private int sendDelay = 0 ; //ms 
        public  int SendDelay
        {
            set
            {
                this.sendDelay = value ;
            }
        }
        #endregion

        #region EsbLogger
        private IEsbLogger esbLogger = null ; 
        public  IEsbLogger EsbLogger
        {
            set
            {
                this.esbLogger = value ;
            }
        }
        #endregion        
        #endregion

        public OverdueMessageSender()
        {            
        }

        public void SendOverdueMessage(string userID)
        {
            IList list = this.overdueMessageHandler.PickupOverdueP2PMessage(userID) ;
            if(list == null)
            {
                return ;
            }

            if(this.sendDelay <=0)
            {
                this.DoSend(list ,userID) ;
            }      
              else
            {
                CbDoSend cb = new CbDoSend(this.DoSend) ;
                cb.BeginInvoke(list ,userID ,null ,null) ;            
            }
        }        

        #region DoSend
        private void DoSend(IList msgList ,string userID )
        {
            try
            {
                if(this.sendDelay >0)
                {
                    System.Threading.Thread.Sleep(this.sendDelay) ;
                }
                
                foreach(NetMessage msg in msgList)
                {
                    int result = this.toClientSender.HookAndSendMessage(userID ,msg) ;
                    if(result != DataSendResult.Succeed)
                    {
                        return ;
                    }
                }

                this.overdueMessageHandler.SetOverdueMessageSended(userID) ;
            }
            catch(Exception ee)
            {
                this.esbLogger.Log(ee.GetType().ToString() ,ee.Message ,"ESFramework.Network.OverdueP2PMessageSender.DoSend" ,ErrorLevel.Standard) ;
            }
        }
        #endregion
    }

    internal delegate void CbDoSend(IList msgList ,string userID) ;

    注意,OverdueMessageSender借助OverdueMessageHandler来取出指定目标用户的OverdueMessage,并借助IToClientSender组件来发送这些OverdueMessage,最后,当发送成功,就调用OverdueMessageHandler的SetOverdueMessageSended方法。

    很多应用都要求在目标用户登录时,将属于他的OverdueMessage发送给它,这个时机可以从IBasicRequestDealer组件的SomeOneLogon事件切入,ESFramework对这一常见需求进行了支持,那就是OverdueMessageBridge。OverdueMessageBridge将OverdueMessageSender链接到这一时机。
   

    public class OverdueMessageBridge
    {

        #region BasicRequestDealer        
        public IBasicRequestDealer BasicRequestDealer
        {
            set
            {
                if(value != null)
                {
                    value.SomeOneLogon += new CbLogon(value_SomeOneLogon);
                }                
            }
        }
        #endregion
        
        #region OverdueMessageSender
        private OverdueMessageSender overdueMessageSender = null ; 
        public  OverdueMessageSender OverdueMessageSender
        {
            set
            {
                this.overdueMessageSender = value ;
            }
        }
        #endregion

        public OverdueMessageBridge()
        {            
        }

        private void value_SomeOneLogon(string userID, NetMessage logonMsg)
        {
            this.overdueMessageSender.SendOverdueMessage(userID) ;
        }
    }

    如果你需要在其它时刻来发送OverdueMessage,自己构造一个对应的Bridge即可。

    细心的朋友可能会问,如果使用了Hook,那么Hook会不会对IOverdueMessageHandler.PickupOverdueP2PMessage()取出的消息产生影响?答案是不会。因为OverdueMessageSender采用的是和P2PMessageDealer一样的方式来发送OverdueMessage,所以如果存在Hook,OverdueMessage也会经过相同的Hook处理。

    (ESFramework V0.2 将会在五一假期后上载)

感谢关注!

上一篇文章:ESFramework介绍之(26)-- 支持复杂插件(InnerDealer 和 InnerDispatcher)

转到  :ESFramework 可复用的通信框架(序) 

时间: 2024-09-20 18:46:50

ESFramework介绍之(27)-- 支持OverdueMessage的相关文章

ESFramework介绍之(17)―― 支持漫游用户和跨区域功能请求

    对于漫游用户的支持和跨区域功能请求的支持是ESFramework最基本的目的之一(回顾),在详细讲述解决方案之前,先了解一下关于这个问题的上下文.    在我们前面讲述的4层C/S架构中,每个AS负责一块区域.比如上海AS负责处理所有目标城市为上海的功能请求和管理所有在上海AS上注册的用户(比如PDA用户或手机用户).如果一个本是在上海注册的用户出差来到了武汉,最方便的,他会连上武汉的AS,这样对于武汉AS来说,这个用户就是漫游用户了.    如果上海的用户登陆上了上海的AS,但是他需要

ESFramework介绍之(26)-- 支持复杂插件(InnerDealer 和 InnerDispatcher)

    (本文内容适合于 ESFramework V0.2+)    通常,最单纯的情况是一个插件对应某一特定类型的功能请求,但是,在有的应用中也会出现这样的情况,有多种类型的功能请求相互关联.并且可能交叉,如果是这样,对应每种类型的请求都开发一个插件可能会非常困难,因为这可能会牵涉到插件之间的相互引用/访问,这违背了插件的"自治"性.最好的办法还是将它们放在一个插件中,通过ServiceItemIndex(你一定还记得消息头定义中除了ServiceKey外还有个ServiceItem

ESFramework介绍之(18)―― Tcp用户管理器组件

    当我们的应用中客户端与AS之间是通过Tcp进行通信的时候,通常,应用也要求管理所有在线的用户.这种管理至少包含以下几点:(1) 当用户上线时,记录上线时间(2) 当用户请求服务时,记录请求服务的时间.服务的类型.本次服务下载的数据量(3) 当用户下线时,记录下线时间.并把本次用户登录.请求服务过程中的所有信息持久化保存(如记录到数据库)     在ESFramework中,实现这种管理的是ITcpUserManager组件,通常,该组件由AS使用(因为在AS.FS.IRAS中只有AS是必

ESFramework介绍之(13)-- 功能插件处理器工厂

    上文讲述的是AS中的基于连接池的消息处理器,现在我们把焦点转移到功能服务器FS上来,看看FS上消息分派的过程.当FS接收到到一个请求后,会从已加载的功能插件列表中选择一个合适的插件来处理这个消息,而每一个功能插件就相当于一个消息处理器.FS和AS的结构一致:    要注意的是,功能服务器FS上收到的所有消息都应该交给功能插件来处理,不存在其它的处理方式.这是使得FS"纯粹"的必须要求.上图已经很清楚的表示了功能插件处理器工厂的位置和作用.它借助插件管理器实现"工厂&q

ESFramework介绍之(9)-- 插件对(Addin Pair)调试“框架”

    使用ESFramework开发C/S(通常为4层.3层也没问题)应用,当需要增加一项新的业务时,我们需要做的仅仅是开发两个插件,一个是服务端的业务功能插件(FunAddin),一个是客户端插件(PassiveAddin),这两个插件合在一起称为Addin Pair.开发这两个插件,只需要关注于业务,而其它与业务无关的比如网络通信.加密.数据安全,都不用管.ESFramework很好的将这些关注点分离开来,使得写"业务"插件的程序员的工作变得非常单纯,在ESFramework介绍

ESFramework介绍之(14)-- AS与FS通信方案

    前面我们已经多次提到,每个AS都有一组FS为之服务(回顾),AS将接收到的功能请求通过Tcp连接池 或Remoting转发给某个FS处理.下面我们将深入讨论AS和FS之间的通信机制.     首先要解决第一个问题,AS如何知道每个为之服务的FS的地址?    最常见的一种解决方案是,AS处的配置文件中有一个FS地址列表,AS每次启动时,就读取这个列表,然后与列表中的每个FS建立Tcp连接池.这种方案很容易实现,但是有很多缺点.最主要的是当动态的添加/移除FS时,都需要修改AS配置文件中的

ESFramework介绍之(8)-- 客户端插件IPassiveAddin

    前文已经提到了,在IServerAgent的基础上,客户端也可以采用插件的结构形式,客户端插件需要实现IPassiveAddin接口.    我的想法是,当客户端主程序加载一个新的PassiveAddin时,可以在某个菜单的子Items上添加一项,当双击这个子菜单项时,则弹出该客户端插件提供的"业务操作窗体".这只是使用客户端插件的可行方式之一,你完全可以根据你的应用来决定使用形式.IPassiveAddin接口定义如下:  1     /// <summary> 

ESFramework介绍之(4)――消息拦截器INetMessageHook

    网络上传输的消息经常是经过加密和压缩,有的特定类型的消息可能还需要进行其它变形,ESFramework通过INetMessageHook对这些功能提供支持.需要说明的是,ESFramework对消息进行截获(Hook)处理有两种方式,一是仅仅Hook处理消息主体(Body),而不对消息头作任何变换:另一种方式是对整个消息(包括消息头和主体)都进行Hook处理.通常,第一种方式已经能够满足我们的大多数应用,并且效率也更高,如果应用有更特殊的要求,可以采用第二种方式.本文先介绍第一种方式,后

ESFramework介绍之(7)-- 服务器代理IServerAgent

    (本文原作于2006.03.15,第一次修正于2006.06.06,修正后适用于ESFramework V0.3+)     (本文是ESFramework对客户端开发的支持特性之一 ,如果要按顺序阅读,请转到ESFramework介绍(序))         分布式系统的构建一般有两种模式,一是基于消息(如Tcp,http等),一是基于方法调用(如RPC.WebService.Remoting).深入想一想,它们其实是一回事.如果你了解过.NET的Proxy,那么你会发现,方法调用和消