zoukankan      html  css  js  c++  java
  • WCF后续之旅(9):通过WCF的双向通信实现Session管理[上篇]

    我们都知道,WCF支持Duplex的消息交换模式,它允许在service的执行过程中实现对client的回调。WCF这种双向通信的方式是我们可以以Event Broker或者订阅/发布的方式来定义和调用WCF Service。今天我们就给大家一个具体的例子:通过WCF的duplex communication方式现在Session管理。

    一、Session 管理提供的具体功能

    我们的例子实现了下面一些Session Management相关的功能:

    • Start/End Session:可以调用service开始一个新的Session或者结束掉一个现有的Session。当开始一个Session的时候,service根据client端传入的client相关的信息(ClientInfo),创建一个SessionInfo对象,该对象由一个GUID类型的SessionID唯一标识,代表一个具体的Client和Service的Session。在service端,通过一个dictionary维护者一个当前所有的active session列表,key为SessionID,value是SessionInfo对象。当client调用相应的service,传入对应的SessionID,该SessionID对应的SessionInfo从该session列表中移除。
    • Session Timeout:如同ASP.NET具有一个Timeout的时间一样,我们的例子也具有timeout的机制。在client可以注册timeout事件,某个session timeout,service会通过在start session中指定的callback回调相应的操作(OnTimeout)并处罚client注册的timeout事件。session timeout后,SessionInfo对象从active session列表中移除。 比如在本例中,我们通过注册事件使得timeout后,程序在显示timeout message之后,自动退出。
    • Session Renew:session timeout判断的依据是client最后活动的时间(last activity time),而该事件反映的是最后一次鼠标操作的时间(假设我们的client是一个GUI应用)。所以从session的生命管理来讲,用户的每次鼠标操作实际上将session的时间延长到session timeout的时间。
    • Session Listing Viewing:Administrator或者某个具有相应权限的用户,可以查看当前活动的session列表和session相关的信息,比如IP地址、主机名称、用户名、session开始的时间和最后一次活动的时间,见下图。
    • Session Killing:如何发现某个用户正在做一些不该做的事情,或者发现当前的并发量太大,管理员可以强行杀掉某个正在活动的Session。同session timeout一样,client端可以注册session killed事件。当session被强行中止后,service回调client相应的方法(OnSessionKilled),触发该事件。比如在本例中,我们通过注册事件使得某个client对应的session被杀掉后,该client程序在显示message之后,自动退出。

    wcf_02_09_01_thumb1

    二、Session Timeout的实现原理

    在该例子中,最重要的是如何实现timeout的功能,而该功能的核心在于如何探测session的状态(Active、Timeout、Killed)。一般地我们有两种截然不同的方式来实现这样的功能:

    1、客户端驱动

    这是大多数人会想得到的方式,通过这样的方式实现session status的检测功能:如下图所示,client端调用相应的service开始一个session,并获得SessionID。client端每隔一定的时间调用相应的操作(CheckSessionStatus),并将自己的SessionID传入,进行session status的检测(步骤1),根据返回的状态进行相应的处理;用户的鼠标操作将会调用相应的操作(RenewSession)将session的last active time修正为service端的当前时间(不应该是client的时间)(步骤2)。然而,不可能每次鼠标操作都进行service的调用,这样会频繁的调用service调用肯定会使程序不堪重负。所以会一般会设置一个service调用的时间间隔,也就是在一定的时间端内,只有一次鼠标操作会触发service的调用。由于CheckSessionStatus和RenewSession的调用都是基于某个时间间隔的,所以实时性是怎么也解决不了的。此外,这种形如轮询方式的机制在高并发的情况下也会让service端的压力正大。

    image_thumb1

    2、服务端驱动

    设计服务端驱动模型是从.NET Remoting的remote instance生命周期管理机制得到的灵感。我们知道和WCF3种InstanceContext Mode(PerCall、PerSession和Single)相对应,Remoting也具有3种不同的对象激活方式(Object Activation):SingleCall、CAO(client activated object)和Singleton。SingleCall和Singleton是两个极端,不需要特殊的对象回收机制,而CAO模式下,Remoting采用了一种基于“租约”(lease)的service instance 生命周期管理机制:remote object被一个租约一个“租约”(lease:实现了System.Runtime.Remoting.Lifetime.ILease interface)对象引用。client端通过一个Sponsor( System.Runtime.Remoting.Lifetime.ISponsor)引用lease对象. 当Lease Manager检测到某个remote object的lease超时,Remoting不会马上对其进行垃圾回收,而是找到该lease的Sponsor对象,通过Sponsor对象回调Renewal方法(Sponsor处于client端),返回一个Timespan对象,表明需要将remote object的lifetime延长的时间,如何该值小于或者等于零,则不需要延长,该对象将会被回收掉;否则将lifetime延长至相应的时间。同时,client的每次远程调用,都会自动实现对lifetime的Renew功能。(详细内容可以参考我的文章:[原创]我所理解的Remoting (2) :远程对象的生命周期管理-Part II)

    我们实现与此相似的Session Management的功能,具体的流程如下图所示:

    image_thumb3

    步骤一

    client端调用Guid StartSession(SessionClientInfo clientInfo, out TimeSpan timeout)方法,其中SessionClientInfo 表述client的一些基本的信息,比如IP地址、主机名称、用户名等等。service端接收到请求后,创建一个SessionInfo对象,该对象代表一个具体基于某个client的session,并同通过一GUID形式的SessionID唯一标识。同时将此SessionClientInfo 对象加入到表示当前所有活动的Session列表中,该列表通过一个dictionary表示(IDictionary<Guid, SessionInfo> CurrentSessionList),其中key是SessionID。最后service将SessionID和session timeout的时间返回到client端。

    此外,client调用StartSession,除了指定SessionClientInfo 之外,还提供了一个Callback对象,Callback用在service在相应的时机(session轮询、session timeout,kill session)实现对client的回调,下面是3个主要的callback操作:

    • TimeSpan Renew():对Session生命周期的延长。
    • void OnSessionKilled(SessionInfo sessionInfo):当client对应的session被杀掉之后,调用该方法实现实时通知。
    • void OnSessionTimeout(SessionInfo sessionInfo):当client对应的session timeout后,调用该方法实现实时通知。

    除了维护一个当前活动session的列表之外,service还维护一个Callback列表(IDictionary<Guid, ISessionCallback> CurrentCallbackList),key仍然是SessionID。当StartSession被调用后,callback被加入到CurrentCallbackList中。

    步骤二

    service以一定的时间间隔对session列表进行轮询(polling),根据SessionClientInfo的最后活动时间(LastActivityTime)和session timeout的时间判断是否需要renew session(DateTime.Now - sessionInfo.LastActivityTime 〉 Timeout)。考虑到对实时性的要求,对于列表中每个session的状态检查都是通过异步的方式同时进行的。

    步骤三

    如何需要进行session renewal,则通过SessionID,从callback列表中找出与此对应的callback对象,调用Renew方法,并返回一个Timespan类型的值,如何该值大于零,表明需要延长session的生命周期,则将SessionInfo的LastActivityTime 加上该值;

    步骤四

    当Renew方法返回Timespan小于或者等于零,表明session真正timeout,则调用callback对象的OnSessionTimeout通知client端session timeout。

    步骤五

    该步骤和上面的步骤二、三、四并没时间上的先后顺序。他的主要功能是,维护一个反映真正最后活动时间的全局变量,每个鼠标操作都将此值设为当前时间(这个通过注册MouseMove事件很容易实现)。对于Renew方法的返回值,就是通过此全局变量和session timeout时间(通过StartSession获得)计算得到:Timeout - (DateTime.Now - LastActivityTime)。

    注:可能有人会说,为什么不将LastActivityTime返回到service端,service将session的LastActivityTime设定成该值就可以了呀?实际上,这样做依赖于这样的一个假设:client端的时间和server端的时间是一致的。很显然,我们不能作出这样的假设。

    三、整个应用的结构

    在介绍具体实现之前,我们先来了解一下整个solution的总体结构:

    wcf_02_09_02_thumb1

    我依然采用我常用的4层结构(Contract、Service、Hosting和Client),其中client采用一个windows application来模拟客户端。熟悉我文章的人应该对这个结果有一定的了解了,在这里就不多做介绍了。

    1、Data Contract、Service Contract和Callback Contract

    我们先来定义一些抽象层的东西Contract, 通过这些contract你会对提供的功能有一个大致的了解,首先来看看在client和service端传输的数据的定义:

    Client端描述:SessionClientInfo

       1: namespace Artech.SessionManagement.Contract
       2: {
       3:     [DataContract]
       4:     public class SessionClientInfo
       5:     {
       6:         [DataMember]
       7:         public string IPAddress{ get; set; } 
       8:  
       9:         [DataMember]
      10:         public string HostName{ get; set; } 
      11:  
      12:         [DataMember]
      13:         public string UserName{ get; set; } 
      14:  
      15:         [DataMember]
      16:         public IDictionary<string, string> ExtendedProperties{ get; set; }
      17:     }
      18: } 

    定义了一个描述述client的基本信息:IP地址、主机名称、用户名,同时定义了一个用于保存额外信息的ExtendedProperties。

    Session的描述:SessionInfo

       1: namespace Artech.SessionManagement.Contract
       2: {
       3:     [DataContract]
       4:     [KnownType(typeof(SessionClientInfo))]
       5:     public class SessionInfo
       6:     {
       7:         [DataMember]
       8:         public Guid SessionID{ get; set; } 
       9:         [DataMember]
      10:         public DateTime StartTime{ get; set; } 
      11:         [DataMember]
      12:         public DateTime LastActivityTime{get;set;} 
      13:         [DataMember]
      14:         public SessionClientInfo ClientInfo{ get; set; } 
      15:         public bool IsTimeout{ get; set; }
      16:     }
      17: } 
      18:  

    定义了Session的基本信息:Session的ID、开始的时间、最后一次活动的时间、客户端基本信息以及表明Session是否Timeout的Flag。

    Callback Contract:ISessionCallback

       1: namespace Artech.SessionManagement.Contract
       2: {    
       3:     public interface ISessionCallback
       4:     {
       5:         [OperationContract]
       6:         TimeSpan Renew(); 
       7:  
       8:         [OperationContract(IsOneWay = true)]
       9:         void OnSessionKilled(SessionInfo sessionInfo); 
      10:  
      11:         [OperationContract(IsOneWay = true)]
      12:         void OnSessionTimeout(SessionInfo sessionInfo);
      13:     }
      14: } 

    Renew()通过获得Session需要延长的时间;OnSessionKilled和OnSessionTimeout实现Session被杀掉和Timeout时的实时通知。

    ServiceContract:ISessionManagement

       1: namespace Artech.SessionManagement.Contract
       2: {
       3:     [ServiceContract(CallbackContract = typeof(ISessionCallback))]
       4:     public interface ISessionManagement
       5:     {
       6:         [OperationContract]
       7:         Guid StartSession(SessionClientInfo clientInfo, out TimeSpan timeout); 
       8:  
       9:         [OperationContract]
      10:         void EndSession(Guid sessionID); 
      11:  
      12:         [OperationContract]
      13:         IList<SessionInfo> GetActiveSessions(); 
      14:  
      15:         [OperationContract]
      16:         void KillSessions(IList<Guid> sessionIDs);
      17:     }
      18: }

    StartSession和EndSession用户Session的启动和中止,GetActiveSessions获得当前所有活动的Sesssion列表,KillSessions用于强行结束一个或多个Session。具体实现请参阅Part II.

     WCF后续之旅: 

    WCF后续之旅(1): WCF是如何通过Binding进行通信的 
    WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel 
    WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher 
    WCF后续之旅(4):WCF Extension Point 概览 
    WCF后续之旅(5): 通过WCF Extension实现Localization 
    WCF后续之旅(6): 通过WCF Extension实现Context信息的传递 
    WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成 
    WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成 
    WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I] 
    WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II] 
    WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance 
    WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity) 
    WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响 
    WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇] 
    WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇] 
    WCF后续之旅(14):TCP端口共享 
    WCF后续之旅(15): 逻辑地址和物理地址 
    WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter) 
    WCF后续之旅(17):通过tcpTracer进行消息的路由

  • 相关阅读:
    nginx能访问html静态文件但无法访问php文件
    LeetCode "498. Diagonal Traverse"
    LeetCode "Teemo Attacking"
    LeetCode "501. Find Mode in Binary Search Tree"
    LeetCode "483. Smallest Good Base" !!
    LeetCode "467. Unique Substrings in Wraparound String" !!
    LeetCode "437. Path Sum III"
    LeetCode "454. 4Sum II"
    LeetCode "445. Add Two Numbers II"
    LeetCode "486. Predict the Winner" !!
  • 原文地址:https://www.cnblogs.com/artech/p/1259607.html
Copyright © 2011-2022 走看看