程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF後續之旅(9):通過WCF的雙向通信實現Session管理[Part I]

WCF後續之旅(9):通過WCF的雙向通信實現Session管理[Part I]

編輯:關於.NET

我們都知道,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之後,自動退出。

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端的壓力正大。

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的功能,具體的流程如下圖所示:

步驟一: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的總體結構:

我依然采用我常用的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。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved