我們還是簡單的來復(fù)習(xí)一下Session吧:Session的數(shù)據(jù)時保存在服務(wù)器端,并且每個客戶端對應(yīng)不同Session。那么Session究竟是如何保存,如何區(qū)分客服端的了?我們還是沿用以前的方法來講吧,以一個demo開始:
protected void Page_Load(object sender, EventArgs e)
{
string name = this.Request["Name"];
object sessionName = Session["Name"];
if (string.IsNullOrEmpty(name) && sessionName==null)
{
Response.Write("Please Enter your name!");
}
else
{
if (sessionName == null)
{
Session.Add("Name", name);
Response.Write("Set Session name and Session ID:"+Session.SessionID);
}
else
{
Response.Write("Get Session Name and Session ID:"+ Session.SessionID);
}
Response.Write(" Name:" + name);
}
}
假設(shè)我們的請求路徑為http://localhost:18385/WebForm1.aspx?name=majiang
第一次請求數(shù)據(jù)如下:
第二次請求數(shù)據(jù)了:
這里我們看見在第一次請求的返回頭里面有一個ASP.NET_SessionId,在第二次請求過程中這個請求頭里面也含有ASP.NET_SessionId,并且它的值剛好是Session.SessionID(我這里用的是asp.net4.5),我們可以猜測這個ASP.NET_SessionId就是用來區(qū)分不同的客戶端請求。那么這個值是什么時候生成的又是什么時候輸出的了?
首先我們需要知道我們用到的那個Session具體在什么地方定義的,其實它是定義于HttpContext的Session屬性中,我們一般的page也只是調(diào)用這個屬性而已。
public HttpSessionState Session
{
get
{
if (this.HasWebSocketRequestTransitionCompleted)
{
return null;
}
if (this._sessionStateModule != null)
{
lock (this)
{
if (this._sessionStateModule != null)
{
this._sessionStateModule.InitStateStoreItem(true);
this._sessionStateModule = null;
}
}
}
return (HttpSessionState) this.Items["AspSession"];
}
}
這里用到一個_sessionStateModule的變量,那么究竟在什么地方操作它們的了?在HttpContext中有兩個操作sessionStateModule方法如下:
internal void AddDelayedHttpSessionState(SessionStateModule module)
{
if (this._sessionStateModule != null)
{
throw new HttpException(SR.GetString("Cant_have_multiple_session_module"));
}
this._sessionStateModule = module;
}
internal void RemoveDelayedHttpSessionState()
{
this._sessionStateModule = null;
}
這兩個方法干什么的我就不說了,它們是在什么地方調(diào)用的了。如果你開發(fā)過asp.net,那么你應(yīng)該知道在SessionStateModule 類,它是一個IHttpModule的實現(xiàn)者專門用來管理Session的,在這個類中有一個InitModuleFromConfig方法,該方法主要是在該類的Init中調(diào)用,如喪我們來看看它的具體實現(xiàn)吧:
private void InitModuleFromConfig(HttpApplication app, SessionStateSection config) { if (config.Mode != SessionStateMode.Off) { app.AddOnAcquireRequestStateAsync(new BeginEventHandler(this.BeginAcquireState), new EndEventHandler(this.EndAcquireState)); app.ReleaseRequestState += new EventHandler(this.OnReleaseState); app.EndRequest += new EventHandler(this.OnEndRequest); this._partitionResolver = this.InitPartitionResolver(config); switch (config.Mode) { case SessionStateMode.InProc: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new InProcSessionStateStore(); this._store.Initialize(null, null); break; case SessionStateMode.StateServer: if (HttpRuntime.UseIntegratedPipeline) { s_canSkipEndRequestCall = true; } this._store = new OutOfProcSessionStateStore(); ((OutOfProcSessionStateStore) this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.SQLServer: this._store = new SqlSessionStateStore(); ((SqlSessionStateStore) this._store).Initialize(null, null, this._partitionResolver); break; case SessionStateMode.Custom: this._store = this.InitCustomStore(config); break; } this._idManager = this.InitSessionIDManager(config); if (((config.Mode == SessionStateMode.InProc) || (config.Mode == SessionStateMode.StateServer)) && this._usingAspnetSessionIdManager) { this._ignoreImpersonation = true; } } }
這里主要是設(shè)置 this._store和 this._idManager 它們兩個變量,其中 this._store的設(shè)置根據(jù)Session的存儲類型不同設(shè)置為不同的實例,這里的存儲方式有以下四種
public enum SessionStateMode
{
Off,
InProc,
StateServer,
SQLServer,
Custom
}
默認(rèn)的是SessionStateMode.InProc,所以默認(rèn)的this._store是一個InProcSessionStateStore實例,而this._idManager默認(rèn)是一個SessionIDManager實例。這個方法結(jié)束后我們的 this._store和 this._idManager這兩個變量就已經(jīng)有值了。在SessionStateModule類中還有一個很重要的方法 BeginAcquireState:
private IAsyncResult BeginAcquireState(object source, EventArgs e, AsyncCallback cb, object extraData) { IAsyncResult result; bool sessionStateItem = true; bool flag3 = false; this._acquireCalled = true; this._releaseCalled = false; this.ResetPerRequestFields(); this._rqContext = ((HttpApplication) source).Context; this._rqAr = new HttpAsyncResult(cb, extraData); this.ChangeImpersonation(this._rqContext, false); try { if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_BEGIN, this._rqContext.WorkerRequest); } this._store.InitializeRequest(this._rqContext); bool requiresSessionState = this._rqContext.RequiresSessionState; if (this._idManager.InitializeRequest(this._rqContext, false, out this._rqSupportSessionIdReissue)) { this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } if ((s_allowInProcOptimization && !s_sessionEverSet) && (!requiresSessionState || !((SessionIDManager) this._idManager).UseCookieless(this._rqContext))) { flag3 = true; } else { this._rqId = this._idManager.GetSessionID(this._rqContext); } if (!requiresSessionState) { if (this._rqId != null) { this._store.ResetItemTimeout(this._rqContext, this._rqId); } this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } this._rqExecutionTimeout = this._rqContext.Timeout; if (this._rqExecutionTimeout == DEFAULT_DBG_EXECUTION_TIMEOUT) { this._rqExecutionTimeout = s_configExecutionTimeout; } this._rqReadonly = this._rqContext.ReadOnlySessionState; if (this._rqId != null) { sessionStateItem = this.GetSessionStateItem(); } else if (!flag3) { bool flag4 = this.CreateSessionId(); this._rqIdNew = true; if (flag4) { if (s_configRegenerateExpiredSessionId) { this.CreateUninitializedSessionState(); } this._rqAr.Complete(true, null, null); if (EtwTrace.IsTraceEnabled(4, 8)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_SESSION_DATA_END, this._rqContext.WorkerRequest); } return this._rqAr; } } if (sessionStateItem) { this.CompleteAcquireState(); this._rqAr.Complete(true, null, null); } result = this._rqAr; } finally { this.RestoreImpersonation(); } return result; }
在這個方法中有以下3句比較重要
this._rqId = this._idManager.GetSessionID(this._rqContext);
sessionStateItem = this.GetSessionStateItem();
this.CompleteAcquireState();
第一句獲取SessionID,第二句貨物SessionStateItem,第三句主要是調(diào)用一個CompleteAcquireState方法,而這個方法里面有一句 SessionStateUtility.AddDelayedHttpSessionStateToContext(this._rqContext, this);或則this.InitStateStoreItem(true); 這個方法主要對應(yīng)一句
SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);,在這個類中還有一個方法OnReleaseState里面有這么一句
SessionStateUtility.RemoveHttpSessionStateFromContext(this._rqContext, delayed);
我們首先來可看看SessionStateUtility的AddHttpSessionStateToContext、RemoveHttpSessionStateFromContext方法的實現(xiàn)吧。
internal static void AddDelayedHttpSessionStateToContext(HttpContext context, SessionStateModule module){ context.AddDelayedHttpSessionState(module);}internal void AddDelayedHttpSessionState(SessionStateModule module){ if (this._sessionStateModule != null) { throw new HttpException(SR.GetString("Cant_have_multiple_session_module")); } this._sessionStateModule = module;}public static void AddHttpSessionStateToContext(HttpContext context, IHttpSessionState container) { HttpSessionState state = new HttpSessionState(container); try { context.Items.Add("AspSession", state); } catch (ArgumentException) { throw new HttpException(SR.GetString("Cant_have_multiple_session_module")); } } internal static void RemoveHttpSessionStateFromContext(HttpContext context, bool delayed) { if (delayed) { context.RemoveDelayedHttpSessionState(); } else { context.Items.Remove("AspSession"); } }
其中HttpContext的RemoveDelayedHttpSessionState就一句 this._sessionStateModule = null;我想對于SessionStateUtility里面的這幾個方法我就不多說吧,很簡單。
我們還是回頭看看前面那2句吧,
public string GetSessionID(HttpContext context){ string id = null; this.CheckInitializeRequestCalled(context); if (this.UseCookieless(context)) { return (string) context.Items["AspCookielessSession"]; } HttpCookie cookie = context.Request.Cookies[Config.CookieName]; if ((cookie != null) && (cookie.Value != null)) { id = this.Decode(cookie.Value); if ((id != null) && !this.ValidateInternal(id, false)) { id = null; } } return id;}
默認(rèn)情況下我們的cookie是可用的,這里的Config.CookieName實際上就是SessionStateSection的CookieName屬性
[ConfigurationProperty("cookieName", DefaultValue="ASP.NET_SessionId")]public string CookieName{ get { return (string) base[_propCookieName]; } set { base[_propCookieName] = value; }}
到這里大家應(yīng)該知道為什么Http請求和返回關(guān)于Session對應(yīng)Cookie的id是ASP.NET_SessionId了吧。不過大家要注意一點這里的SessionIDManager 在操作cookie做了一些數(shù)據(jù)驗證處理,如果在特殊情況需要自定義驗證規(guī)則我們可以自己來實現(xiàn)ISessionIDManager接口。這里我們可以看到第一次請求是沒有sessionid的,所以sessionStateItem = this.GetSessionStateItem();這句代碼不會執(zhí)行,sessionStateItem默認(rèn)為true,但是第二次請求時有sessionid這句代碼就會執(zhí)行。GetSessionStateItem()的實現(xiàn)這里我們就忽略了吧,這個方法設(shè)置一個SessionStateStoreData的實例 this._rqItem ,如果 this._rqItem為null則返回false。一般我們的Session都是可讀寫的。GetSessionStateItem方法主要是調(diào)用 this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
現(xiàn)在我們回到CompleteAcquireState方法中來:
if (flag)
{
SessionStateUtility.AddDelayedHttpSessionStateToContext(this._rqContext, this);
this._rqSessionState = s_delayedSessionState;
}
else
{
this.InitStateStoreItem(true); //SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);
}
這里是flag默認(rèn)是false,里面具體判斷就不說,InitStateStoreItem方法主要代碼:
if (this._rqItem == null)
{
this._rqItem = this._store.CreateNewStoreData(this._rqContext, s_timeout);
}
this._rqSessionItems = this._rqItem.Items;
this._rqSessionState = new HttpSessionStateContainer(this, this._rqId, this._rqSessionItems, this._rqStaticObjects, this._rqItem.Timeout, this._rqIsNewSession, s_configCookieless, s_configMode, this._rqReadonly);
SessionStateUtility.AddHttpSessionStateToContext(this._rqContext, this._rqSessionState);
這里InProcSessionStateStore 的CreateNewStoreData方法實際就是調(diào)用SessionStateUtility.CreateLegitStoreData:
internal static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout){ if (sessionItems == null) { sessionItems = new SessionStateItemCollection(); } if ((staticObjects == null) && (context != null)) { staticObjects = GetSessionStaticObjects(context); } return new SessionStateStoreData(sessionItems, staticObjects, timeout);}
其中SessionStateItemCollection的定義如下:
public sealed class SessionStateItemCollection : NameObjectCollectionBase, ISessionStateItemCollection, ICollection, IEnumerable
這里創(chuàng)建了一個 HttpSessionStateContainer實例。我想大家到這里就應(yīng)該明白我們的Session實際上就是一個HttpSessionStateContainer實例。
好現(xiàn)在我來看 Session.SessionID這個是怎么實現(xiàn)的
public string SessionID
{
get
{
if (this._id == null)
{
this._id = this._stateModule.DelayedGetSessionId();
}
return this._id;
}
}
而SessionStateModule的DelayedGetSessionId方法實現(xiàn)如下:
internal string DelayedGetSessionId()
{
this.ChangeImpersonation(this._rqContext, false);
try
{
this._rqId = this._idManager.GetSessionID(this._rqContext);
if (this._rqId == null)
{
this.CreateSessionId();
}
}
finally
{
this.RestoreImpersonation();
}
return this._rqId;
}
這里的CreateSessionId具體是怎么創(chuàng)建我就不說了吧,知道它是真正創(chuàng)建sessionid的就可以。而session的實際操作都是在ISessionStateItemCollection里面如HttpSessionStateContainer的Add方法:
public void Add(string name, object value)
{
this._sessionItems[name] = value;
}
而這里的_sessionItems實際上是this._rqItem.Items,本來想忽略_rqItem的創(chuàng)建,看來這個實例比較強啊。
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
if ((((this._rqItem == null) && !flag2) && (this._rqId != null)) && ((s_configCookieless != HttpCookieMode.UseUri) || !s_configRegenerateExpiredSessionId))
{
this.CreateUninitializedSessionState();
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span, out this._rqLockId, out this._rqActionFlags);
}
這里的CreateUninitializedSessionState方法實際就是調(diào)用this._store.CreateUninitializedItem(this._rqContext, this._rqId, s_timeout);
我們前面知道this._store這個可以取很多實例的,是SessionStateStoreProviderBase類型,這里我們也已默認(rèn)的 InProcSessionStateStore(繼承SessionStateStoreProviderBase)來說說吧,相關(guān)方法:
private SessionStateStoreData DoGet(HttpContext context, string id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags) { bool flag; string key = this.CreateSessionStateCacheKey(id); InProcSessionState state = (InProcSessionState) HttpRuntime.CacheInternal.Get(key); if (state == null) { return null; } ...... return SessionStateUtility.CreateLegitStoreData(context, state._sessionItems, state._staticObjects, state._timeout); } public override void CreateUninitializedItem(HttpContext context, string id, int timeout) { string key = this.CreateSessionStateCacheKey(id); SessionIDManager.CheckIdLength(id, true); InProcSessionState state = new InProcSessionState(null, null, timeout, false, DateTime.MinValue, NewLockCookie, 1); try { } finally { if (HttpRuntime.CacheInternal.UtcAdd(key, state, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, timeout, 0), CacheItemPriority.NotRemovable, this._callback) == null) { PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_TOTAL); PerfCounters.IncrementCounter(AppPerfCounter.SESSIONS_ACTIVE); } } }
現(xiàn)在我們終于明白一個Sessionid對應(yīng)一個SessionStateStoreData,所以它能區(qū)分不同的用戶請求,這里的id就是我們前面的this._rqId了。
現(xiàn)在我們也總結(jié)一下吧,我們的HttpContext的Session屬性實際上是一個HttpSessionStateContainer實例(HttpSessionStateContainer繼承IHttpSessionState),而它數(shù)據(jù)成員都是保存在ISessionStateItemCollection實例中,每一次http請求我們都會去獲取它的Sessionid,第一次請求sessionid問null,我們沒有對應(yīng)的SessionStateStoreData數(shù)據(jù),這時我們在SessionStateModule的 InitStateStoreItem方法調(diào)用SessionStateStoreProviderBase的CreateNewStoreData方法來創(chuàng)建一個SessionStateStoreData實例,其中該實例有一個成員變量類型是ISessionStateItemCollection用來保存用戶session的數(shù)據(jù)。同一個用戶第二次請求我們能獲取到它的sessionid,默認(rèn)也能獲取到SessionStateStoreData實例(session過期則取不到)。一個用戶對應(yīng)一個SessionStateStoreData,每個SessionStateStoreData里面有一個ISessionStateItemCollection實例用來保存用戶數(shù)據(jù),至于sessionid也就是用戶身份的區(qū)別依賴于ISessionIDManager的實現(xiàn)。