配色: 字号:
通过WCF双向通信实现Session管理
2016-10-06 | 阅:  转:  |  分享 
  
通过WCF双向通信实现Session管理

一、SessionManagementService的实现

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



1:namespaceArtech.SessionManagement.Service

2:{

3:publicstaticclassSessionManager

4:{

5:privatestaticobject_syncHelper=newobject();

6:

7:internalstaticTimeSpanTimeout

8:{get;set;}

9:

10:publicstaticIDictionaryCurrentSessionList

11:{get;set;}

12:

13:publicstaticIDictionaryCurrentCallbackList

14:{get;set;}

15:

16:staticSessionManager()

17:{

18:stringsessionTimeout=ConfigurationManager.AppSettings["SessionTimeout"];

19:if(string.IsNullOrEmpty(sessionTimeout))

20:{

21:thrownewConfigurationErrorsException("Thesessiontimeoutapplicationsettingismissing");

22:}

23:

24:doubletimeoutMinute;

25:if(!double.TryParse(sessionTimeout,outtimeoutMinute))

26:{

27:thrownewConfigurationErrorsException("Thesessiontimeoutapplicationsettingshouldbeofdoubdletype.");

28:}

29:

30:Timeout=newTimeSpan(0,0,(int)(timeoutMinute60));

31:CurrentSessionList=newDictionary();

32:CurrentCallbackList=newDictionary();

33:}

34://...

35:}

36:}

37:

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



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





































































1:publicstaticGuidStartSession(SessionClientInfoclientInfo)

2:{

3:GuidsessionID=Guid.NewGuid();

4:ISessionCallbackcallback=OperationContext.Current.GetCallbackChannel();

5:SessionInfosesionInfo=newSessionInfo(){SessionID=sessionID,StartTime=DateTime.Now,LastActivityTime=DateTime.Now,ClientInfo=clientInfo};

6:lock(_syncHelper)

7:{

8:CurrentSessionList.Add(sessionID,sesionInfo);

9:CurrentCallbackList.Add(sessionID,callback);

10:}

11:returnsessionID;

12:}

13:

14:publicstaticvoidEndSession(GuidsessionID)

15:{

16:if(!CurrentSessionList.ContainsKey(sessionID))

17:{

18:return;

19:}

20:

21:lock(_syncHelper)

22:{

23:CurrentCallbackList.Remove(sessionID);

24:CurrentSessionList.Remove(sessionID);

25:}

26:}

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



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

























1:publicstaticvoidKillSessions(IListsessionIDs)

2:{

3:lock(_syncHelper)

4:{

5:foreach(GuidsessionIDinsessionIDs)

6:{

7:if(!CurrentSessionList.ContainsKey(sessionID))

8:{

9:continue;

10:}

11:

12:SessionInfosessionInfo=CurrentSessionList[sessionID];

13:CurrentSessionList.Remove(sessionID);

14:CurrentCallbackList[sessionID].OnSessionKilled(sessionInfo);

15:CurrentCallbackList.Remove(sessionID);

16:}

17:}

18:}

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



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



1:[MethodImpl(MethodImplOptions.Synchronized)]

2:publicstaticvoidRenewSessions()

3:{

4:IListwaitHandleList=newList();

5:

6:foreach(varsessioninCurrentSessionList)

7:{

8:RenewSessionrenewsession=delegate(KeyValuePairsessionInfo)

9:{

10:if(DateTime.Now-sessionInfo.Value.LastActivityTime
11:{

12:return;

13:}

14:try

15:{

16:TimeSpanrenewDuration=CurrentCallbackList[sessionInfo.Key].Renew();

17:if(renewDuration.TotalSeconds>0)

18:{

19:sessionInfo.Value.LastActivityTime+=renewDuration;

20:}

21:else

22:{

23:sessionInfo.Value.IsTimeout=true;

24:CurrentCallbackList[session.Key].OnSessionTimeout(sessionInfo.Value);

25:}

26:}

27:catch(CommunicationObjectAbortedException)

28:{

29:sessionInfo.Value.IsTimeout=true;

30:return;

31:}

32:};

33:

34:IAsyncResultresult=renewsession.BeginInvoke(session,null,null);

35:waitHandleList.Add(result.AsyncWaitHandle);

36:}

37:

38:if(waitHandleList.Count==0)

39:{

40:return;

41:}

42:WaitHandle.WaitAll(waitHandleList.ToArray());

43:ClearSessions();

44:}

45:

46:publicdelegatevoidRenewSession(KeyValuePairsession);

47:

首先我定义了一个delegate:RenewSession,来实现表示对单个session的renew操作。在RenewSessions方法中,我们遍历CurrentSessionList中的每个SessionInfo对象,根据LastActivityTime判断是否需要对该Session进行Renew操作(DateTime.Now-sessionInfo.Value.LastActivityTime


在这里有3点需要注意:



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



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



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



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



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



1:namespaceArtech.SessionManagement.Service

2:{

3:[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Multiple)]

4:publicclassSessionManagementService:ISessionManagement

5:{

6:#regionISessionManagementMembers

7:

8:publicGuidStartSession(SessionClientInfoclientInfo,outTimeSpantimeout)

9:{

10:timeout=SessionManager.Timeout;

11:returnSessionManager.StartSession(clientInfo);

12:}

13:

14:publicvoidEndSession(GuidsessionID)

15:{

16:SessionManager.EndSession(sessionID);

17:}

18:

19:publicIListGetActiveSessions()

20:{

21:returnnewList(SessionManager.CurrentSessionList.Values.ToArray());

22:}

23:

24:publicvoidKillSessions(IListsessionIDs)

25:{

26:SessionManager.KillSessions(sessionIDs);

27:}

28:

29:#endregion

30:}

31:}

32:

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



二、ServiceHosting

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



对SessionManagementService的Host。

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

1:namespaceArtech.SessionManagement.Hosting

2:{

3:classProgram

4:{

5:staticvoidMain(string[]args)

6:{

7:using(ServiceHosthost=newServiceHost(typeof(SessionManagementService)))

8:{

9:host.Opened+=delegate

10:{

11:Console.WriteLine("Thesessionmanagementservicehasbeenstartedup!");

12:};

13:host.Open();

14:

15:Timertimer=newTimer(

16:delegate{SessionManager.RenewSessions();},null,0,5000);

17:

18:Console.Read();

19:}

20:}

21:}

22:}

23:

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



1:

2:

3:

4:

5:


6:

7:

8:

9:

10:

11:

12:

13:


14:


15:

16:


17:


18:


19:

三、如何定义Client

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



1:namespaceArtech.SessionManagement.Client

2:{

3:publicstaticclassSessionUtility

4:{

5:staticSessionUtility()

6:{

7:Callback=newSessionCallback();

8:Channel=newDuplexChannelFactory(Callback,"sessionservice").CreateChannel();

9:}

10:

11:privatestaticISessionManagementChannel

12:{get;set;}

13:

14:privatestaticISessionCallbackCallback

15:{get;set;}

16:

17:publicstaticDateTimeLastActivityTime

18:{get;set;}

19:

20:publicstaticGuidSessionID

21:{get;set;}

22:

23:publicstaticTimeSpanTimeout

24:{get;set;}

25:

26:publicstaticvoidStartSession(SessionClientInfoclientInfo)

27:{

28:TimeSpantimeout;

29:SessionID=Channel.StartSession(clientInfo,outtimeout);

30:Timeout=timeout;

31:}

32:

33:publicstaticIListGetActiveSessions()

34:{

35:returnChannel.GetActiveSessions();

36:}

37:

38:publicstaticvoidKillSessions(IListsessionIDs)

39:{

40:Channel.KillSessions(sessionIDs);

41:}

42:}

43:}

44:

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



1:publicclassSessionCallback:ISessionCallback

2:{

3:#regionISessionCallbackMembers

4:

5:publicTimeSpanRenew()

6:{

7:returnSessionUtility.Timeout-(DateTime.Now-SessionUtility.LastActivityTime);

8:}

9:

10:publicvoidOnSessionKilled(SessionInfosessionInfo)

11:{

12:MessageBox.Show("Thecurrentsessionhasbeenkilled!",sessionInfo.SessionID.ToString(),MessageBoxButtons.OK,MessageBoxIcon.Information);

13:Application.Exit();

14:}

15:

16:publicvoidOnSessionTimeout(SessionInfosessionInfo)

17:{

18:MessageBox.Show("Thecurrentsessiontimeout!",sessionInfo.SessionID.ToString(),MessageBoxButtons.OK,MessageBoxIcon.Information);

19:Application.Exit();

20:}

21:

22:#endregion

23:}

24:

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



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



wcf_02_09_01_thumb1



这是StartSession按钮的clickeventhandler:



1:privatevoidbuttonStartSession_Click(objectsender,EventArgse)

2:{

3:stringhostName=Dns.GetHostName();

4:IPAddress[]ipAddressList=Dns.GetHostEntry(hostName).AddressList;

5:stringipAddress=string.Empty;

6:foreach(IPAddressaddressinipAddressList)

7:{

8:if(address.AddressFamily==AddressFamily.InterNetwork)

9:{

10:ipAddress+=address.ToString()+";";

11:}

12:}

13:ipAddress=ipAddress.TrimEnd(";".ToCharArray());

14:

15:stringuserName=this.textBoxUserName.Text.Trim();

16:if(string.IsNullOrEmpty(userName))

17:{

18:return;

19:}

20:

21:SessionClientInfoclientInfo=newSessionClientInfo(){IPAddress=ipAddress,HostName=hostName,UserName=userName};

22:SessionUtility.StartSession(clientInfo);

23:this.groupBox2.Enabled=false;

24:}

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



“GetAllActiveSession”,获取当前所有的活动的session,绑定到Datagrid:



1:privatevoidbuttonGet_Click(objectsender,EventArgse)

2:{

3:IListactiveSessions=SessionUtility.GetActiveSessions();

4:this.dataGridViewSessionList.DataSource=activeSessions;

5:foreach(DataGridViewRowrowinthis.dataGridViewSessionList.Rows)

6:{

7:GuidsessionID=(Guid)row.Cells["SessionID"].Value;

8:row.Cells["IPAddress"].Value=activeSessions.Where(session=>session.SessionID==sessionID).ToList()[0].ClientInfowww.baiyuewang.net.IPAddress;

9:row.Cells["UserName"].Value=activeSessions.Where(session=>session.SessionID==sessionID).ToList()[0].ClientInfo.UserName;

10:}

11:}

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



1:privatevoidbuttonKill_Click(objectsender,EventArgse)

2:{

3:IListsessionIDs=newList();

4:foreach(DataGridViewRowrowinthis.dataGridViewSessionList.Rows)

5:{

6:if((string)row.Cells["Select"].Value=="1")

7:{

8:GuidsessionID=newGuid(row.Cells["SessionID"].Value.ToString());

9:if(sessionID==SessionUtility.SessionID)

10:{

11:MessageBox.Show("Youcannotkillyourcurrentsession!","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);

12:return;

13:}

14:sessionIDs.Add(sessionID);

15:}

16:}

17:

18:SessionUtility.KillSessions(sessionIDs);

19:}

20:

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



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



1:privatevoidRegisterMouseMoveEvent(Controlcontrol)

2:{

3:control.MouseHover+=delegate

4:{

5:SessionUtility.LastActivityTime=DateTime.Now;

6:};

7:

8:foreach(Controlchildincontrol.Controls)

9:{

10:this.RegisterMouseMoveEvent(child);

11:}

12:}

13:

14:privatevoidFormSessionManagement_Load(objectsender,EventArgse)

15:{

16:this.dataGridViewSessionList.AutoGenerateColumns=false;

17:this.RegisterMouseMoveEvent(this);

18:}

19:

献花(0)
+1
(本文系thedust79首藏)