阅读230 返回首页    go 阿里云 go 技术社区[云栖]


WCF后续之旅(9): 通过WCF双向通信实现Session管理[下篇]

一、Session Management Service的实现

   1: namespace Artech.SessionManagement.Service
   2: {
   3:     public static class SessionManager
   4:     {
   5:         private static object _syncHelper = new object(); 
   6:  
   7:         internal static TimeSpan Timeout
   8:         { get; set; } 
   9:  
  10:         public static IDictionary<Guid, SessionInfo> CurrentSessionList
  11:         { get; set; } 
  12:  
  13:         public static IDictionary<Guid, ISessionCallback> CurrentCallbackList
  14:         { get; set; } 
  15:  
  16:         static SessionManager()
  17:         {
  18:             string sessionTimeout = ConfigurationManager.AppSettings["SessionTimeout"];
  19:             if (string.IsNullOrEmpty(sessionTimeout))
  20:             {
  21:                 throw new ConfigurationErrorsException("The session timeout application setting is missing");
  22:             } 
  23:  
  24:             double timeoutMinute;
  25:             if (!double.TryParse(sessionTimeout, out timeoutMinute))
  26:             {
  27:                 throw new ConfigurationErrorsException("The session timeout application setting should be of doubdle type.");
  28:             }           
  29:  
  30:             Timeout = new TimeSpan(0, 0, (int)(timeoutMinute * 60));
  31:             CurrentSessionList = new Dictionary<Guid, SessionInfo>();
  32:             CurrentCallbackList = new Dictionary<Guid, ISessionCallback>();
  33:         }
  34:         //...
  35:   }
  36: }
  37:  

   1: public static Guid StartSession(SessionClientInfo clientInfo)
   2: {
   3:     Guid sessionID = Guid.NewGuid();
   4:     ISessionCallback callback = OperationContext.Current.GetCallbackChannel<ISessionCallback>();
   5:     SessionInfo sesionInfo = new SessionInfo() { 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:     return sessionID;
  12: } 
  13:  
  14: public static void EndSession(Guid sessionID)
  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: } 

   1: public static void KillSessions(IList<Guid> sessionIDs)
   2: {
   3:     lock (_syncHelper)
   4:     {
   5:         foreach (Guid sessionID in sessionIDs)
   6:         {
   7:             if (!CurrentSessionList.ContainsKey(sessionID))
   8:             {
   9:                 continue;
  10:             } 
  11:  
  12:             SessionInfo sessionInfo = CurrentSessionList[sessionID];
  13:             CurrentSessionList.Remove(sessionID);
  14:             CurrentCallbackList[sessionID].OnSessionKilled(sessionInfo);
  15:             CurrentCallbackList.Remove(sessionID);
  16:         }
  17:     }
  18: } 

   1: [MethodImpl(MethodImplOptions.Synchronized)]
   2: public static void RenewSessions()
   3: {
   4:     IList<WaitHandle> waitHandleList = new List<WaitHandle>(); 
   5:  
   6:     foreach (var session in CurrentSessionList)
   7:     {
   8:         RenewSession renewsession = delegate(KeyValuePair<Guid, SessionInfo> sessionInfo)
   9:         {
  10:             if (DateTime.Now - sessionInfo.Value.LastActivityTime < Timeout)
  11:             {
  12:                 return;
  13:             }
  14:             try
  15:             {
  16:                 TimeSpan renewDuration = 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:         IAsyncResult result = 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<WaitHandle>());
  43:             ClearSessions();
  44: } 
  45:  
  46: public delegate void RenewSession(KeyValuePair<Guid, SessionInfo> session); 
  47:  

   1: namespace Artech.SessionManagement.Service
   2: {
   3:     [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode =ConcurrencyMode.Multiple)]
   4:    public class SessionManagementService:ISessionManagement
   5:     {
   6:         #region ISessionManagement Members 
   7:  
   8:         public Guid StartSession(SessionClientInfo clientInfo,out TimeSpan timeout)
   9:         {
  10:             timeout = SessionManager.Timeout;
  11:             return SessionManager.StartSession(clientInfo);
  12:         } 
  13:  
  14:         public void EndSession(Guid sessionID)
  15:         {
  16:             SessionManager.EndSession(sessionID);
  17:         } 
  18:  
  19:         public IList<SessionInfo> GetActiveSessions()
  20:         {
  21:             return new List<SessionInfo>(SessionManager.CurrentSessionList.Values.ToArray<SessionInfo>());     
  22:         } 
  23:  
  24:         public void KillSessions(IList<Guid> sessionIDs)
  25:         {
  26:             SessionManager.KillSessions(sessionIDs);
  27:         } 
  28:  
  29:         #endregion
  30:     }
  31: } 
  32:  

二、Service Hosting

   1: namespace Artech.SessionManagement.Hosting
   2: {
   3:     class Program
   4:     {
   5:         static void Main(string[] args)
   6:         {
   7:             using (ServiceHost host = new ServiceHost(typeof(SessionManagementService)))
   8:             { 
   9:                 host.Opened += delegate
  10:                 {
  11:                     Console.WriteLine("The session management service has been started up!");
  12:                 };
  13:                 host.Open(); 
  14:  
  15:                 Timer timer = new Timer(
  16:                     delegate { SessionManager.RenewSessions(); }, null, 0, 5000); 
  17:  
  18:                 Console.Read();
  19:             }
  20:         }
  21:     }
  22: } 
  23:  

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <appSettings>
   4:         <add key="SessionTimeout" value="0.5"/>
   5:     </appSettings>
   6:     <system.serviceModel>
   7:         <services>
   8:             <service name="Artech.SessionManagement.Service.SessionManagementService">
   9:                 <endpoint binding="netTcpBinding" bindingConfiguration="" contract="Artech.SessionManagement.Contract.ISessionManagement" />
  10:                 <host>
  11:                     <baseAddresses>
  12:                         <add baseAddress="net.tcp://127.0.0.1:9999/sessionservice" />
  13:                     </baseAddresses>
  14:                 </host>
  15:             </service>
  16:         </services>
  17:     </system.serviceModel>
  18: </configuration> 
  19:  

三、如何定义Client

   1: namespace Artech.SessionManagement.Client
   2: {
   3:     public static class SessionUtility
   4:     {
   5:         static SessionUtility()
   6:         {
   7:             Callback = new SessionCallback();
   8:             Channel = new DuplexChannelFactory<ISessionManagement>(Callback, "sessionservice").CreateChannel();            
   9:         } 
  10:  
  11:         private static ISessionManagement Channel
  12:         { get; set; } 
  13:  
  14:         private static ISessionCallback Callback
  15:         { get; set; } 
  16:  
  17:         public static DateTime LastActivityTime
  18:         { get; set; } 
  19:  
  20:         public static Guid SessionID
  21:         { get; set; } 
  22:  
  23:         public static TimeSpan Timeout
  24:         { get; set; } 
  25:  
  26:         public static void StartSession(SessionClientInfo clientInfo)
  27:         {
  28:             TimeSpan timeout;
  29:             SessionID = Channel.StartSession(clientInfo, out timeout);
  30:             Timeout = timeout;
  31:         } 
  32:  
  33:         public static IList<SessionInfo> GetActiveSessions()
  34:         {
  35:             return Channel.GetActiveSessions();
  36:         } 
  37:  
  38:         public static void KillSessions(IList<Guid> sessionIDs)
  39:         {
  40:             Channel.KillSessions(sessionIDs);
  41:         }
  42:     }
  43: } 
  44:  

   1: public class SessionCallback : ISessionCallback
   2: {
   3:     #region ISessionCallback Members 
   4:  
   5:     public TimeSpan Renew()
   6:     {
   7:         return SessionUtility.Timeout - (DateTime.Now - SessionUtility.LastActivityTime);
   8:     } 
   9:  
  10:     public void OnSessionKilled(SessionInfo sessionInfo)
  11:     {
  12:         MessageBox.Show("The current session has been killed!", sessionInfo.SessionID.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information);
  13:         Application.Exit();
  14:     } 
  15:  
  16:     public void OnSessionTimeout(SessionInfo sessionInfo)
  17:     {
  18:         MessageBox.Show("The current session timeout!", sessionInfo.SessionID.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information);
  19:         Application.Exit();
  20:     } 
  21:  
  22:     #endregion
  23: } 
  24:  

wcf_02_09_01_thumb1

   1: private void buttonStartSession_Click(object sender, EventArgs e)
   2: {
   3:     string hostName = Dns.GetHostName();
   4:     IPAddress[] ipAddressList = Dns.GetHostEntry(hostName).AddressList;
   5:     string ipAddress = string.Empty;
   6:     foreach (IPAddress address in ipAddressList)
   7:     {
   8:         if (address.AddressFamily == AddressFamily.InterNetwork)
   9:         {
  10:             ipAddress += address.ToString() + ";";
  11:         }
  12:     }
  13:     ipAddress = ipAddress.TrimEnd(";".ToCharArray()); 
  14:  
  15:     string userName = this.textBoxUserName.Text.Trim();
  16:     if (string.IsNullOrEmpty(userName))
  17:     {
  18:         return;
  19:     } 
  20:  
  21:     SessionClientInfo clientInfo = new SessionClientInfo() { IPAddress = ipAddress, HostName = hostName, UserName = userName };
  22:     SessionUtility.StartSession(clientInfo);
  23:     this.groupBox2.Enabled = false;
  24: } 

   1: private void buttonGet_Click(object sender, EventArgs e)
   2: {
   3:     IList<SessionInfo> activeSessions = SessionUtility.GetActiveSessions();
   4:     this.dataGridViewSessionList.DataSource = activeSessions;
   5:     foreach (DataGridViewRow row in this.dataGridViewSessionList.Rows)
   6:     {
   7:         Guid sessionID = (Guid)row.Cells["SessionID"].Value;
   8:         row.Cells["IPAddress"].Value = activeSessions.Where(session=> session.SessionID == sessionID).ToList<SessionInfo>()[0].ClientInfo.IPAddress;
   9:         row.Cells["UserName"].Value = activeSessions.Where(session => session.SessionID == sessionID).ToList<SessionInfo>()[0].ClientInfo.UserName;
  10:     }
  11: } 

   1: private void buttonKill_Click(object sender, EventArgs e)
   2: {
   3:     IList<Guid> sessionIDs = new List<Guid>();
   4:     foreach ( DataGridViewRow row in this.dataGridViewSessionList.Rows)
   5:     {
   6:         if ((string)row.Cells["Select"].Value == "1")
   7:         {
   8:             Guid sessionID = new Guid(row.Cells["SessionID"].Value.ToString());
   9:             if (sessionID == SessionUtility.SessionID)
  10:             {
  11:                 MessageBox.Show("You cannot kill your current session!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  12:                 return;
  13:             }
  14:             sessionIDs.Add(sessionID);
  15:         }
  16:     }   
  17:  
  18:     SessionUtility.KillSessions(sessionIDs);           
  19: } 
  20:  

   1: private void RegisterMouseMoveEvent(Control control)
   2: {
   3:     control.MouseHover += delegate
   4:     {
   5:         SessionUtility.LastActivityTime = DateTime.Now;
   6:     }; 
   7:  
   8:     foreach (Control child in control.Controls)
   9:     {
  10:         this.RegisterMouseMoveEvent(child);
  11:     }
  12: } 
  13:  
  14: private void FormSessionManagement_Load(object sender, EventArgs e)
  15: {
  16:     this.dataGridViewSessionList.AutoGenerateColumns = false;
  17:     this.RegisterMouseMoveEvent(this);
  18: } 
  19:  

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

image_thumb[5]

 

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进行消息的路由


作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及着作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

最后更新:2017-10-30 17:04:38

  上一篇:go  WCF后续之旅(9):通过WCF的双向通信实现Session管理[上篇]
  下一篇:go  WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance