现在的位置: 首页 > 综合 > 正文

WCF后续之旅(9):通过WCF的双向通信实现Session管理

2014年10月11日 ⁄ 综合 ⁄ 共 20720字 ⁄ 字号 评论关闭

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

1、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 
2、Session Timeout的实现原理

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

I、客户端驱动:这是大多数人会想得到的方式,通过这样的方式实现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

II、服务端驱动:设计服务端驱动模型是从.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端的时间是一致的。很显然,我们不能作出这样的假设。

 

3、整个应用的结构

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

wcf_02_09_02_thumb1

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

4、Data Contract、Service Contract和Callback Contract

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

I、Client的:SessionClientInfo

namespace Artech.SessionManagement.Contract
{
    [DataContract]
    public class SessionClientInfo
    {
        [DataMember]
        public string IPAddress
        { get; set; }

        [DataMember]
        public string HostName
        { get; set; }

        [DataMember]
        public string UserName
        { get; set; }

        [DataMember]
        public IDictionary<string, string> ExtendedProperties
        { get; set; }
    }
}

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

II、Session的描述:SessionInfo

namespace Artech.SessionManagement.Contract
{
    [DataContract]
    [KnownType(typeof(SessionClientInfo))]
    public class SessionInfo
    {
        [DataMember]
        public Guid SessionID
        { get; set; }

        [DataMember]
        public DateTime StartTime
        { get; set; }

        [DataMember]
        public DateTime LastActivityTime
        {get;set;}

        [DataMember]
        public SessionClientInfo ClientInfo
        { get; set; }

        public bool IsTimeout
        { get; set; }
    }
}

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

III、Callback Contract:ISessionCallback

namespace Artech.SessionManagement.Contract
{    
    public interface ISessionCallback
    {
        [OperationContract]
        TimeSpan Renew();

        [OperationContract(IsOneWay = true)]
        void OnSessionKilled(SessionInfo sessionInfo);

        [OperationContract(IsOneWay = true)]
        void OnSessionTimeout(SessionInfo sessionInfo);
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

IV、ServiceContract:ISessionManagement

namespace Artech.SessionManagement.Contract
{
    [ServiceContract(CallbackContract = typeof(ISessionCallback))]
    public interface ISessionManagement
    {
        [OperationContract]
        Guid StartSession(SessionClientInfo clientInfo, out TimeSpan timeout);

        [OperationContract]
        void EndSession(Guid sessionID);

        [OperationContract]
        IList<SessionInfo> GetActiveSessions();

        [OperationContract]
        void KillSessions(IList<Guid> sessionIDs);
    }
}

 

StartSession和EndSession用户Session的启动和中止,GetActiveSessions获得当前所有活动的Sesssion列表,KillSessions用于强行结束一个或多个Session。

 

 

 

5、Session Management Service的实现

现在我们来看看Session Management真正的实现,和我以前的例子不同,我不是把所有的实现都写在WCF service上,而是定义了另一个class来实现所有的业务逻辑:SessionManager。我们分析一下具体的实现逻辑。

namespace Artech.SessionManagement.Service
{
    public static class SessionManager
    {
        private static object _syncHelper = new object();

        internal static TimeSpan Timeout
        { get; set; }

        public static IDictionary<Guid, SessionInfo> CurrentSessionList
        { get; set; }

        public static IDictionary<Guid, ISessionCallback> CurrentCallbackList
        { get; set; }

        static SessionManager()
        {
            string sessionTimeout = ConfigurationManager.AppSettings["SessionTimeout"];
            if (string.IsNullOrEmpty(sessionTimeout))
            {
                throw new ConfigurationErrorsException("The session timeout application setting is missing");
            }

            double timeoutMinute;
            if (!double.TryParse(sessionTimeout, out timeoutMinute))
            {
                throw new ConfigurationErrorsException("The session timeout application setting should be of doubdle type.");
            }          

            Timeout = new TimeSpan(0, 0, (int)(timeoutMinute * 60));
            CurrentSessionList = new Dictionary<Guid, SessionInfo>();
            CurrentCallbackList = new Dictionary<Guid, ISessionCallback>();
        }

        … … … … … … … … … … … … … … … …… … … … … … … … … … … … … … … …
    }
}

首先来看Field、Property和static constructor的定义。_syncHelper 用于实现多线程同步之用;Timeout是session timeout的时间,可配置;CurrentSessionList和CurrentCallbackList两个dictionary在上面我们已经作过介绍,分别代表当前活动的session列表和callback列表,key均为SessionID。在静态构造函数中,初始化session timeout的时间,和实例化CurrentSessionList和CurrentCallbackList。

接着我们来看看StartSession和EndSession两个方法,这两个方法分别代表Session的开始和结束。

public static Guid StartSession(SessionClientInfo clientInfo)
{
    Guid sessionID = Guid.NewGuid();
    ISessionCallback callback = OperationContext.Current.GetCallbackChannel<ISessionCallback>();
    SessionInfo sesionInfo = new SessionInfo() { SessionID = sessionID, StartTime = DateTime.Now,  LastActivityTime = DateTime.Now, ClientInfo = clientInfo };
    lock (_syncHelper)
    {
        CurrentSessionList.Add(sessionID, sesionInfo);
        CurrentCallbackList.Add(sessionID, callback);
    }
    return sessionID;
}

public static void EndSession(Guid sessionID)
{
    if (!CurrentSessionList.ContainsKey(sessionID))
    {
        return;
    }

    lock (_syncHelper)
    {
        CurrentCallbackList.Remove(sessionID);
        CurrentSessionList.Remove(sessionID);
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

在StartSession方法中,首先创建一个GUID作为SessionID。通过OperationContext.Current获得callback对象,并根据client端传入的SessionClientInfo 对象创建SessionInfo 对象,最后将callback对象和SessionInfo 对象加入CurrentCallbackList和CurrentSessionList中。由于这两个集合会在多线程的环境下频繁地被访问,所以在对该集合进行添加和删除操作时保持线程同是显得尤为重要,所在在本例中,所有对列表进行添加和删除操作都需要获得_syncHelper加锁下才能执行。与StartSession相对地,EndSession方法仅仅是将SessionID标识的callback对象和SessionInfo 对象从列表中移除。

然后我们来看看如何强行中止掉一个或多个活动的session:KillSessions。

public static void KillSessions(IList<Guid> sessionIDs)
{
    lock (_syncHelper)
    {
        foreach (Guid sessionID in sessionIDs)
        {
            if (!CurrentSessionList.ContainsKey(sessionID))
            {
                continue;
            }

            SessionInfo sessionInfo = CurrentSessionList[sessionID];
            CurrentSessionList.Remove(sessionID);
            CurrentCallbackList[sessionID].OnSessionKilled(sessionInfo);
            CurrentCallbackList.Remove(sessionID);
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

逻辑很简单,就是先从CurrentSessionList中获得对应的SessionInfo 对象,然后将其从CurrentSessionList中移除,然后根据SessionID获得对用的Callback对象,调用OnSessionKilled方法实时通知client session被强行中止,最后将callback对象从CurrentCallbackList中清楚。需要注意的是OnSessionKilled是One-way方式调用的,所以是异步的,时间的消耗可以忽略不计,也不会抛出异常,所以对_syncHelper的锁会很开释放,所以不会对并发造成太大的影响。

Session的管理最终要、也是作复杂的事对Timeout的实现,再我们的例子中,我们通过定期对CurrentSessionList中的每个session进行轮询实现。每次轮询通过RenewSessions方法实现,我们来看看该方法的定义:

[MethodImpl(MethodImplOptions.Synchronized)]
public static void RenewSessions()
{
    IList<WaitHandle> waitHandleList = new List<WaitHandle>();

    foreach (var session in CurrentSessionList)
    {
        RenewSession renewsession = delegate(KeyValuePair<Guid, SessionInfo> sessionInfo)
        {
            if (DateTime.Now - sessionInfo.Value.LastActivityTime < Timeout)
            {
                return;
            }
            try
            {
                TimeSpan renewDuration = CurrentCallbackList[sessionInfo.Key].Renew();
                if (renewDuration.TotalSeconds > 0)
                {
                    sessionInfo.Value.LastActivityTime += renewDuration;
                }
                else
                {
                    sessionInfo.Value.IsTimeout = true;
                    CurrentCallbackList[session.Key].OnSessionTimeout(sessionInfo.Value);
                }
            }
            catch (CommunicationObjectAbortedException)
            {
                sessionInfo.Value.IsTimeout = true;
                return;
            }
        };

        IAsyncResult result = renewsession.BeginInvoke(session, null, null);
        waitHandleList.Add(result.AsyncWaitHandle);
    }

    if (waitHandleList.Count == 0)
    {
        return;
    }
            WaitHandle.WaitAll(waitHandleList.ToArray<WaitHandle>());
            ClearSessions();
}

public delegate void RenewSession(KeyValuePair<Guid, SessionInfo> session);

首先我定义了一个delegate:RenewSession,来实现表示对单个session的renew操作。在RenewSessions方法中,我们遍历CurrentSessionList中的每个SessionInfo对象,根据LastActivityTime判断是否需要对该Session进行Renew操作(DateTime.Now - sessionInfo.Value.LastActivityTime < Timeout,意味着单单从server来看,Session都尚未过期),如何需要,则通过SessionID从CurrentCallbackList中取出callback对象,调用Renew方法。如何返回的的Timespan大于零,则表明,client端需要延长session的生命周期,则让LastActivityTime 加上该值。如何返回的值小于零,表明session真的过期了,那么通过调用callback对象的OnSessionTimeout方法实现对client的实时的通知,并将SessionInfo对象的IsTimeout 设置为true。等所以得操作结束之后,在将IsTimeout 为true的SessionInfo对象和对应的callback对象从列表中移除。

在这里有3点需要注意:

1)由于在client过多的情况下,CurrentSessionList得数量太多,按照同步的方式逐个进行状态的检测、callback的调用可以需要很长的时间,会严重影响实时性。所以我们采用的是异步的方式,这是通过将操作定义到RenewSession delegate中,并掉用BeginInvoke方法实现的。

2)在调用Callback的Renew方法的时候,很有可以client端的程序已经正常或者非正常关闭,在这种情况下会抛出CommunicationObjectAbortedException异常,我们应该把这种情况视为timeout。所以我们也将IsTimeout 设置为true。

3)我们之所以现在遍历之后才对session进行清理,主要考虑到我们的操作时在对线程环境中执行,如何在并发操作的情况下对集合进行删除,会出现一些意想不到的不同步情况下。我们通过WaitHandle保证所有的并发操作都结束了:我先创建了一个IList<WaitHandle>对象waitHandleList ,将每个基于session对象的异步操作的WaitHandle添加到该列表(waitHandleList.Add(result.AsyncWaitHandle);)通过

WaitHandle.WaitAll(waitHandleList.ToArray<WaitHandle>());保证所有的操作都结束了。

有了SessionManager,我们的Service就显得很简单了:

namespace Artech.SessionManagement.Service
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode =ConcurrencyMode.Multiple)]
   public class SessionManagementService:ISessionManagement
    {
        #region ISessionManagement Members

        public Guid StartSession(SessionClientInfo clientInfo,out TimeSpan timeout)
        {
            timeout = SessionManager.Timeout;
            return SessionManager.StartSession(clientInfo);
        }

        public void EndSession(Guid sessionID)
        {
            SessionManager.EndSession(sessionID);
        }

        public IList<SessionInfo> GetActiveSessions()
        {
            return new List<SessionInfo>(SessionManager.CurrentSessionList.Values.ToArray<SessionInfo>());     
        }

        public void KillSessions(IList<Guid> sessionIDs)
        {
            SessionManager.KillSessions(sessionIDs);
        }

        #endregion
    }
}

基本上就是调用SessionManager对应的方法。

6、Service Hosting

在Artech.SessionManagement.Hosting.Program中的Main()方法中,实际上是做了两件事情:

I、对SessionManagementService的Host。

II、通过Timer对象实现对Session列表的定期(5s)轮询。

namespace Artech.SessionManagement.Hosting
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(SessionManagementService)))
            { 
                host.Opened += delegate
                {
                    Console.WriteLine("The session management service has been started up!");
                };
                host.Open();

                Timer timer = new Timer(
                    delegate { SessionManager.RenewSessions(); }, null, 0, 5000);

                Console.Read();
            }
        }
    }
}

这是configuration,除了system.serviceModel相关配置外,还定义了配置了session timeout的时间,单位为”分”:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        
<add key="SessionTimeout" value="0.5"/>
    </appSettings>
    <system.serviceModel>
        <services>
            <service name="Artech.SessionManagement.Service.SessionManagementService">
                <endpoint binding="netTcpBinding" bindingConfiguration="" contract="Artech.SessionManagement.Contract.ISessionManagement" />
                <host>
                    <baseAddresses>
                        <add baseAddress="net.tcp://127.0.0.1:9999/sessionservice" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

7、如何定义Client

这个service的实现已经完成,我们最后来介绍如何根据service的特点来定义我们的client程序了。我们的client是一个GUI应用(WinForm)。为了简便,我们把所有的逻辑定义在一个facade class上面:SessionUtility。

namespace Artech.SessionManagement.Client
{
    public static class SessionUtility
    {
        static SessionUtility()
        {
            Callback = new SessionCallback();
            Channel = new DuplexChannelFactory<ISessionManagement>(Callback, "sessionservice").CreateChannel();            
        }

        private static ISessionManagement Channel
        { get; set; }

        private static ISessionCallback Callback
        { get; set; }

        public static DateTime LastActivityTime
        { get; set; }

        public static Guid SessionID
        { get; set; }

        public static TimeSpan Timeout
        { get; set; }

        public static void StartSession(SessionClientInfo clientInfo)
        {
            TimeSpan timeout;
            SessionID = Channel.StartSession(clientInfo, out timeout);
            Timeout = timeout;
        }

        public static IList<SessionInfo> GetActiveSessions()
        {
            return Channel.GetActiveSessions();
        }

        public static void KillSessions(IList<Guid> sessionIDs)
        {
            Channel.KillSessions(sessionIDs);
        }
    }
}

SessionUtility定义了连个public property:SessionID代表当前session的ID,Timeout代表Session timeout的时间,这两个属性都在StartSession中被初始化,而LastActivityTime代表的是最后一次用户交互的时间。上面的代码和简单,在这里就不多作介绍了。这里需要着重介绍我们的Callback class:

public class SessionCallback : ISessionCallback
{
    #region ISessionCallback Members

    public TimeSpan Renew()
    {
        return SessionUtility.Timeout - (DateTime.Now - SessionUtility.LastActivityTime);
    }

    public void OnSessionKilled(SessionInfo sessionInfo)
    {
        MessageBox.Show("The current session has been killed!", sessionInfo.SessionID.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information);
        Application.Exit();
    }

    public void OnSessionTimeout(SessionInfo sessionInfo)
    {
        MessageBox.Show("The current session timeout!", sessionInfo.SessionID.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information);
        Application.Exit();
    }

    #endregion
}

Renew()方法根据Timeout 和LastActivityTime计算出需要对该session延长的时间;OnSessionKilled和OnSessionTimeout在通过MessageBox显示相应的message后将程序退出。

我们简单简单一下本例子提供的client application。具有一个Form。我们把所有的功能集中在该Form中:开始一个新session、获得所有的活动的session列表、强行中止一个或多个Session。

wcf_02_09_01_thumb1

这是StartSession按钮的click event handler:

private void buttonStartSession_Click(object sender, EventArgs e)
{
    string hostName = Dns.GetHostName();
    IPAddress[] ipAddressList = Dns.GetHostEntry(hostName).AddressList;
    string ipAddress = string.Empty;
    foreach (IPAddress address in ipAddressList)
    {
        if (address.AddressFamily == AddressFamily.InterNetwork)
        {
            ipAddress += address.ToString() + ";";
        }
    }
    ipAddress = ipAddress.TrimEnd(";".ToCharArray());

    string userName = this.textBoxUserName.Text.Trim();
    if (string.IsNullOrEmpty(userName))
    {
        return;
    }

    SessionClientInfo clientInfo = new SessionClientInfo() { IPAddress = ipAddress, HostName = hostName, UserName = userName };
    SessionUtility.StartSession(clientInfo);
    this.groupBox2.Enabled = false;
}

获得当前PC的主机名称和IP地址,连同输入的user name创建SessionClientInfo 对象,调用SessionUtility的StartSession开始新的Session。

“Get All Active Session”,获取当前所有的活动的session,绑定到Datagrid:

private void buttonGet_Click(object sender, EventArgs e)
{
    IList<SessionInfo> activeSessions = SessionUtility.GetActiveSessions();
    this.dataGridViewSessionList.DataSource = activeSessions;
    foreach (DataGridViewRow row in this.dataGridViewSessionList.Rows)
    {
        Guid sessionID = (Guid)row.Cells["SessionID"].Value;
        row.Cells["IPAddress"].Value = activeSessions.Where(session=> session.SessionID == sessionID).ToList<SessionInfo>()[0].ClientInfo.IPAddress;
        row.Cells["UserName"].Value = activeSessions.Where(session => session.SessionID == sessionID).ToList<SessionInfo>()[0].ClientInfo.UserName;
    }
}

“Kill Selected Session”按钮被点击,强行中止选中的Session:

private void buttonKill_Click(object sender, EventArgs e)
{
    IList<Guid> sessionIDs = new List<Guid>();
    foreach ( DataGridViewRow row in this.dataGridViewSessionList.Rows)
    {
        if ((string)row.Cells["Select"].Value == "1")
        {
            Guid sessionID = new Guid(row.Cells["SessionID"].Value.ToString());
            if (sessionID == SessionUtility.SessionID)
            {
                MessageBox.Show("You cannot kill your current session!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            sessionIDs.Add(sessionID);
        }
    }  

    SessionUtility.KillSessions(sessionIDs);           
}

由于不能中止自己当前的Session,所以当选中的列表中包含自己的SessionID,会显示一个messagebox提示不应该杀掉属于自己session。

到这里,实际上还有一件重要的事情没有解决,那就是如何动态修正SessionUtility.LastActivityTime。我们希望的事SessionUtility.LastActivityTime能够真正反映最后一次用户交互的时间。为此我们递归地注册每个control的MouseMove事件:

private void RegisterMouseMoveEvent(Control control)
{
    control.MouseHover += delegate
    {
        SessionUtility.LastActivityTime = DateTime.Now;
    };

    foreach (Control child in control.Controls)
    {
        this.RegisterMouseMoveEvent(child);
    }
}

private void FormSessionManagement_Load(object sender, EventArgs e)
{
    this.dataGridViewSessionList.AutoGenerateColumns = false;
    this.RegisterMouseMoveEvent(this);
}

如何你运行我们程序,输入user name开始session后,如果在30s内没有任何鼠标操作,下面的MessageBox将会弹出,当你点击OK按钮,程序会退出。
image_thumb[1]

如何你同时开启多个client端程序,点击“Kill Selected Session”按钮,将会列出所有的Active session,就象我们在上面的截图所示的一样。你可以选择某个session,然后通过点击“Kill selected sessions”按钮强行中止它。通过另一个client application将马上得到反馈:弹出下面一个MessageBox。当你点击OK按钮,程序会退出

image_thumb[5]

抱歉!评论已关闭.