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:
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按钮,程序会退出。
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