【IT168技术】今天来说说 Session 。这个东西嘛,我想每个Asp.net开发人员都知道它,尤其是初学Asp.net时,肯定也用过它,因为用它保存会话数据确实非常简单。本文将对session的来龙去脉做一个介绍。
当我们新建一个网站时,VS20XX 生成的网站模板代码中,Session就是打开。是的,如果你没有关闭它,Session其实是一直在工作着。 您只需要在Page中用一行代码就能判断您的网站是否在使用Session,
很简单,就是写一下Session,如果代码能运行,不出现异常,就表示您的网站是支持Session的。我们可以去web.config从全局关闭它,
再运行上面的代码,就能看到黄页了。换句话说:当您访问Session时发生以下异常即表示您的网站(或者当前页面)是不支持Session的。
这里要说明一下:如果您在某个页面中访问Session时,出现以上黄页,也有可能是页面级别关闭了Session 。在每个aspx页的Page指令行,只要我们设置一下EnableSessionState即可,这个属性有3个可选项。我创建了三个页面,分别接受IDE给的默认名称。
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" EnableSessionState="True" Inherits="_Default" %>
// Default2.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" EnableSessionState="ReadOnly" Inherits="Default2" %>
// Default3.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default3.aspx.cs" EnableSessionState="False" Inherits="Default3" %>
对于Default.aspx来说,EnableSessionState这个设置可以不用显式指定,因为它就是默认值。 页面的这个参数的默认值也可以在web.config中设置,如:
以上三个设置就分别设置了三个不同的Session使用方法。下面我们再来看一下,这个设置对于Session来说,是如何起作用的。
如果您的web.config中有如下设置:
那么,可以在x:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\websiteName\xxxxxx\xxxxxxxx中找到这么三个aspx页面的【编译前版本】:
说明:Asp.net的编译临时目录也可以在web.config中指定,如
public partial class _Default : System.Web.SessionState.IRequiresSessionState {
// Default2.aspx
public partial class Default2 : System.Web.SessionState.IRequiresSessionState, System.Web.SessionState.IReadOnlySessionState {
// Default3.aspx
public partial class Default3 {
或者您也可以编译整个网站,从生成的程序集去看这些类的定义,也能看到以上结果。
也就是说:Page指令中的设置被编译器转成一些接口【标记】,那么,您或许有点好奇,为什么搞这么几个接口,它们在哪里被使用?下面我们来看看这个问题,当然了,也只能反编译.net framework的代码找线索了。最终发现在Application的PostMapRequestHandler事件中
{
void HttpApplication.IExecutionStep.Execute()
{
HttpContext context = this._application.Context;
HttpRequest request = context.Request;
// .................... 注意下面这个调用
context.Handler = this._application.MapHttpHandler(
context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
// ....................
}
}
接着找HttpContext的Handler属性
{
set
{
this._handler = value;
// ...........................
if( this._handler != null ) {
if( this._handler is IRequiresSessionState ) {
this.RequiresSessionState = true;
}
if( this._handler is IReadOnlySessionState ) {
this.ReadOnlySessionState = true;
}
// ...........................
}
}
}
至此,应该大致搞清楚了,原来这二个接口也只是一个标记。我们可以看一下它们的定义:
{
}
public interface IReadOnlySessionState : IRequiresSessionState
{
}
完全就是个空接口,仅仅只是为了区分使用Session的方式而已。可能您会想HttpContext的这二个属性RequiresSessionState, ReadOnlySessionState又是在哪里被使用的。答案就是在SessionStateModule中。 SessionStateModule就是实现Session的HttpModule ,它会检查了所有请求,根据HttpContext的这二个属性分别采用不同的处理方式。大致是如下方法:
// 后面会有一些针对requiresSessionState的判断
if( !requiresSessionState ) {
// .......................
}
this._rqReadonly = this._rqContext.ReadOnlySessionState;
// 后面会有一些针对this._rqReadonly的判断
if( this._rqReadonly ) {
this._rqItem = this._store.GetItem(this._rqContext, this._rqId, out flag2, out span,
out this._rqLockId, out this._rqActionFlags);
}
else {
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span,
out this._rqLockId, out this._rqActionFlags);
// ..........................
}
这块的代码比较散,为了对这二个参数有个权威的说明,我将直接引用MSDN中的原文。
上面的说法提到了锁定,既然有锁定,就会影响并发。我们再看看MSDN中关于并发的解释。
ASP.NET 应用程序是多线程的,因此可支持对多个并发请求的响应。多个并发请求可能会试图访问同一会话信息。假设有这样一种情况,框架集中的多个框架全部引用同一应用程序中的 ASP.NET 网页。框架集中每个框架的独立请求可以在 Web 服务器的不同线程上并发执行。如果每个框架的 ASP.NET 页都访问会话状态变量,则可能会有多个线程并发访问会话存储区。为避免会话存储区中的数据冲突和意外的会话状态行为, SessionStateModule 和 SessionStateStoreProviderBase 类提供了一种功能,能在执行 ASP.NET 页期间以独占方式锁定特定会话的会话存储项。 请注意,如果 EnableSessionState 属性标记为 ReadOnly,则不会对会话存储项设置锁定。但是,同一应用程序中的其他 ASP.NET 页也许可以写入会话存储区,因此对存储区中只读会话数据的请求可能仍然必须等待锁定数据被释放。
在对 GetItemExclusive 方法的调用中,请求开始时即对会话存储数据设置锁定。请求完成后,在调用 SetAndReleaseItemExclusive 方法期间释放锁定。
如果 SessionStateModule 实例在调用 GetItemExclusive 或 GetItem 方法过程中遇到锁定的会话数据,则该实例每隔半秒重新请求一次该会话数据,直到锁定被释放或 ExecutionTimeout 属性中指定的时间已经过去。如果请求超时,SessionStateModule 将调用 ReleaseItemExclusive 方法来释放会话存储数据,然后立即请求该会话存储数据。
为当前响应调用 SetAndReleaseItemExclusive 方法之前,锁定的会话存储数据可能已经在单独的线程上由对 ReleaseItemExclusive 方法的调用释放。这可能导致 SessionStateModule 实例设置和释放已经由其他会话释放和修改的会话状态存储数据。为避免这种情况,SessionStateModule 为每个请求都提供一个锁定标识符,以便修改锁定的会话存储数据。仅当数据存储区中的锁定标识符与 SessionStateModule 提供的锁定标识符匹配时,会话存储数据才能修改。
在权威文字面前,我再解释就显得是多余的。不过,通过我上面的代码分析及MSDN解释,我们可以明白三点:
①它说明了,为什么在Application的一系列事件中,PostMapRequestHandler事件要早于AcquireRequestState事件的原因。因为SessionStateModule要访问HttpContext.RequiresSessionState,但是这个属性又要等到给HttpContext.Handler赋值后才能获取到,而HttpContext.Handler的赋值操作是在PostMapRequestHandler事件中完成的,有意思吧。
②如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用。
③使用Session时,尤其是采用默认设置时,会影响并发访问。