日期:2013-12-14  浏览次数:20436 次

在 Web 应用程序这样的无状态环境中,了解会话状态的概念并没有实际的意义。尽管如此,有效的状态管理对于大多数 Web 应用程序来说都是一个必备的功能。Microsoft® ASP.NET 以及许多其他服务器端编程环境都提供了一个抽象层,允许应用程序基于每个用户和每个应用程序存储持久性数据。

需要特别注意的是,Web 应用程序的会话状态是应用程序在不同的请求中缓存和检索的数据。会话表示用户在与该站点连接期间发送的所有请求,会话状态是用户在会话期间生成和使用的持久性数据的集合。每个会话的状态都彼此独立,而且在用户会话结束时就不复存在了。

会话状态与构成 HTTP 协议和规范的任何逻辑实体都没有对应关系。会话是由服务器端开发环境(例如传统的 ASP 和 ASP.NET)构建的抽象层。ASP.NET 展示会话状态的方式以及会话状态的内部实现方式都取决于平台的基础结构。因此,传统的 ASP 和 ASP.NET 以完全不同的方式来实现会话状态,预计在下一版的 ASP.NET 中会有进一步的改进和增强。

本文讨论如何在 ASP.NET 1.1 中实现会话状态,以及如何在被管理的 Web 应用程序中优化会话状态管理。

ASP.NET 会话状态概述
会话状态并不是 HTTP 基础结构的一部分。也就是说,应该有一个结构组件将会话状态与每个传入请求绑定在一起。运行时环境(传统的 ASP 或 ASP.NET)能够接受 Session 之类的关键字,并使用它指示服务器上存储的数据块。要成功解析 Session 对象的调用,运行时环境必须将会话状态添加到正在处理的请求的调用上下文中。完成此操作的方式因平台而异,但它是有状态 Web 应用程序的基础操作。

在传统的 ASP 中,会话状态是作为 asp.dll 库中包含自由线程 COM 对象来实现的。(您对此很好奇吗?其实该对象的 CLSID 是 D97A6DA0-A865-11cf-83AF-00A0C90C2BD8。)此对象存储以名称/值对集合的方式组织的数据。“名称”占位符表示用来检索信息的关键字,而“值”占位符表示会话状态中存储的内容。名称/值对按照会话 ID 进行分组,这样,每个用户看到的只是他/她自己创建的名称/值对。

在 ASP.NET 中,会话状态的编程接口与传统的 ASP 几乎是相同的。但它们的基础实现是完全不同的,前者比后者更具有灵活性、可扩展性和更强的编程功能。深入研究 ASP.NET 会话状态之前,让我们简单回顾一下 ASP.NET 会话基础结构的某些结构功能。

在 ASP.NET 中,任何传入 HTTP 请求都要通过 HTTP 模块管道进行传输。每个模块都可以筛选并修改请求所携带的大量信息。与每个请求关联的信息叫做“调用上下文”,编程中用 HttpContext 对象来表示。我们不应将请求的上下文视为状态信息的另一个容器,虽然它提供的 Items 集合只是一个数据容器。HttpContext 对象不同于所有其他状态对象(例如,Session、Application 和 Cache),因为它的有限生命周期超出了处理请求所需的时间。当请求通过一系列注册的 HTTP 模块后,其 HttpContext 对象将包含状态对象的引用。当最终可以处理请求时,关联的调用上下文将绑定到特定会话 (Session) 和全局状态对象(Application 和 Cache)。

负责设置每个用户的会话状态的 HTTP 模块为 SessionStateModule。该模块的结构是根据 IHttpModule 接口设计的,它为 ASP.NET 应用程序提供大量与会话状态有关的服务。包括生成会话 ID、Cookieless 会话管理、从外部状态提供程序中检索会话数据以及将数据绑定到请求的调用上下文。

HTTP 模块并不在内部存储会话数据。会话状态始终保存在名为“状态提供程序”的外部组件中。状态提供程序完全封装会话状态数据,并通过 IStateClientManager 接口的方法与其他部分进行通信。会话状态 HTTP 模块调用该接口上的方法来读取并保存会话状态。ASP.NET 1.1 支持三种不同的状态提供程序,如表 1 所示。

表 1:状态客户端提供程序

提供程序 说明
InProc 会话值在 ASP.NET 辅助进程(Microsoft® Windows Server™ 2003 中的 aspnet_wp.exe 或 w3wp.exe)的内存中保持为活动对象。这是默认选项。
StateServer 会话值被序列化并存储在单独进程 (aspnet_state.exe) 的内存中。该进程还可以在其他计算机上运行。
SQLServer 会话值被序列化并存储在 Microsoft® SQL Server™ 表中。SQL Server 的实例可以在本地运行,也可以远程运行。

会话状态 HTTP 模块将从 web.config 文件的 <sessionState> 部分读取当前选定的状态提供程序。

<sessionState mode="InProc | StateServer | SQLServer />

根据 mode 特性的值,将通过不同的步骤从不同的进程中检索会话状态并将其存储到不同的进程中。默认情况下,会话状态存储在本地的 ASP.NET 辅助进程中。特殊情况下,会将其存储在 ASP.NET Cache 对象的专用槽中(不能通过编程方式访问)。也可以将会话状态存储在外部,甚至是远程进程中(例如,名为 aspnet_state.exe 的 Windows NT 服务中)。第三个选项是将会话状态存储到由 SQL Server 2000 管理的专用数据库表中。

HTTP 模块会在请求的一开始对会话值进行反序列化,使它们成为词典对象。然后,将采用编程方式通过类(例如,HttpContext 和 Page)显示的属性 Session 来访问词典(实际上是 HttpSessionState 类型的对象)。会话状态值与开发人员可见的会话对象之间的绑定将持续到请求结束。如果请求成功完成,所有状态值将被序列化回状态提供程序,并可用于其他请求。

图 1 说明了请求的 ASP.NET 页面与会话值之间的通信。每个页面所使用的代码都与 page 类上的 Session 属性有联系。其编程方式与传统的 ASP 几乎相同。



图 1:ASP.NET 1.1 中的会话状态体系结构

在完成请求所需的时间内,会话状态的物理值处于锁定状态。该锁定由 HTTP 模块在内部管理并用于同步对会话状态的访问。

会话状态模块实例化应用程序的状态提供程序,并使用从 web.config 文件中读取的信息对其进行初始化。接下来,每个提供程序将继续自己的初始化操作。提供程序的类型不同,其初始化操作会大不相同。例如,SQL Server 状态管理器将打开与给定数据库的连接,而进程外管理器将检查指定的 TCP 端口。另一方面,InProc 状态管理器将存储对回调函数的引用。从缓存中删除元素时将执行此操作,并用于触发应用程序的 Session_OnEnd 事件。

同步访问会话状态
当 Web 页对 Session 属性进行非常简单且直观的调用时,究竟会出现什么情况呢?许多操作都是在后台进行的,如下面的繁琐代码所示:

int siteCount = Convert.ToInt32(Session["Counter"]);

上述代码实际上访问的是 HTTP 模块创建的会话值在本地内存中的副本,从特定状态提供程序(参见图 1)中读取数据。如果其他页面也试图同步访问该会话状态,又会如何呢?这种情况下,当前的请求可能会停止处理不一致的数据或过时的数据。为了避免这种情况,会话状态模块将实现一个读取器/写入器锁定机制,并对状态值的访问进行排队。对会话状态具有写入权限的页面将保留该会话的写入器锁定,直到请求终止。

通过将 @Page 指令的 EnableSessionState 属性设置为 true,页面可以请求会话状态的写入权限。(这是默认设置)。但是,页面还可以拥有会话状态的只读权限,例如,当 EnableSessionState 属性被设置为 ReadOnly 时。在这种情况下,模块将保留该会话的读取器锁定,直到该页面的请求结束。结果将发生并发读取。

如果页面请求设置一个读取器锁定,同一会话中同时处理的其他请求将无法更新会话状态,但是至少可以进行读取。也就是说,如果当前正在处理会话的只读请求,那么等候的只读请求要比需要完全访问权限的请求具有更高的优先权。如果页面请求为会话状态设置一个写入器锁定,那么所有其他页面都将被阻止,无论它们是否要读取或写入内容。例如,如果同时有两个框架试图在 Session 中写入内容,一个框架必须等到另一个框架完成后才能写入。