ASP.NET 中实现会话状态的基础

简介

在 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 对象不同于所有其他状态对象(例如,SessionApplicationCache),因为它的有限生命周期超出了处理请求所需的时间。当请求通过一系列注册的 HTTP 模块后,其 HttpContext 对象将包含状态对象的引用。当最终可以处理请求时,关联的调用上下文将绑定到特定会话 (Session) 和全局状态对象(ApplicationCache)。

负责设置每个用户的会话状态的 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.exew3wp.exe)的内存中保持为活动对象。这是默认选项。
StateServer 会话值被序列化并存储在单独进程 (aspnet_state.exe) 的内存中。该进程还可以在其他计算机上运行。
SQLServer 会话值被序列化并存储在 Microsoft® SQL Server™ 表中。SQL Server 的实例可以在本地运行,也可以远程运行。

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

图 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 中写入内容,一个框架必须等到另一个框架完成后才能写入。

比较状态提供程序

默认情况下,ASP.NET 应用程序将会话状态存储在辅助进程的内存中,特别是 Cache 对象的专用槽中。选中 InProc 模式时,会话状态将存储在 Cache 对象内的槽中。此槽被标记为专用槽,无法通过编程方式进行访问。换句话说,如果枚举 ASP.NET 数据缓存中的所有项目,将不会返回类似于给定会话状态的任何对象。Cache 对象提供两类槽:专用槽和公用槽。编程人员可以添加和处理公用槽,但专用槽只能由系统(特别是 system.web 部件中定义的类)专用。

每个活动会话的状态都占用缓存中的一个专用槽。槽的名称根据会话 ID 进行命名,其值是名为 SessionStateItem 的内部未声明类的一个实例。InProc 状态提供程序获取会话 ID 并在缓存中检索对应的元素。然后将 SessionStateItem 对象的内容输入 HttpSessionState 词典对象,并由应用程序通过 Session 属性进行访问。请注意,ASP.NET 1.0 中存在一个错误,使 Cache 对象的专用槽可以通过编程方式进行枚举。如果您在 ASP.NET 1.0 下运行以下代码,则能够枚举与每个当前活动会话状态中包含的对象对应的项目。

foreach(DictionaryEntry elem in Cache)
{
   Response.Write(elem.Key + ": " + elem.Value.ToString());
}

此错误已经在 ASP.NET 1.1 中得到解决,当您枚举缓存的内容时,将不再列出任何系统槽。

到目前为止,InProc 可能是最快的访问选项。但请记住,会话中存储的数据越多,Web 服务器所消耗的内存就越多,这样会潜在地增加性能降低的风险。如果您计划使用任何进程外解决方案,应该认真考虑一下序列化和反序列化可能带来的影响。进程外解决方案使用 Windows NT 服务 (aspnet_state.exe) 或 SQL Server 表来存储会话值。因此,会话状态保留在 ASP.NET 辅助进程之外,并且需要使用额外的代码层,在会话状态和实际的存储介质之间进行序列化和反序列化操作。只要处理请求就会发生此操作,而且随后必须对其进行最高程度的优化。

因为需要将会话数据从外部储备库复制到本地会话词典中,所以请求导致性能下降了 15%(进程外)到 25% (SQL Server)。请注意,虽然这只是一种粗略的估计,但它应该接近于最低程度的影响,最高程度的影响将远高于此。实际上,这种估计并没有完全考虑到会话状态中实际保存的类型的复杂程度。

在进程外存储方案中,会话状态存活的时间较长,使应用程序的功能更强大,因为它可以防止 Microsoft® Internet 信息服务 (IIS) 和 ASP.NET 失败。通过将会话状态与应用程序相分离,您还可以更容易地将现有应用程序扩展到 Web Farm 和 Web Garden 体系结构中。另外,会话状态存储在外部进程中,从根本上消除了由于进程循环而导致的周期性数据丢失的风险。

下面介绍如何使用 Windows NT 服务。正如上文所述,NT 服务是一个名为 aspnet_state.exe 的进程,通常位于 C:/WINNT/Microsoft.NET/Framework/v1.1.4322 文件夹中。

实际目录取决于您实际运行的 Microsoft® .NET Framework 版本。使用状态服务器之前,应确保该服务就绪并正运行在用作会话存储设备的本地或远程计算机上。状态服务是 ASP.NET 的组成部分并与之一起安装,因此您无需运行其他安装程序。默认情况下,状态服务并没有运行,需要手动启动。ASP.NET 应用程序将在加载状态服务器之后立即尝试与之建立连接。因此,该服务必须准备就绪且正在运行,否则将引发 HTTP 异常。下图显示了该服务的属性对话框。

图 2:ASP.NET 状态服务器的属性对话框

ASP.NET 应用程序需要指定会话状态服务所在的计算机的 TCP/IP 地址。必须将以下设置输入该应用程序的 web.config 文件中。


    
        
    

stateConnectionString 特性包含计算机的 IP 地址以及用来进行数据交换的端口。默认的计算机地址为 127.0.0.1(本地主机),默认端口为 42424。您也可以按名称指示计算机。对于代码来说,使用本地或远程计算机是完全透明的。请注意,不能在该名称中使用非 ASCII 字符,并且端口号是强制的。

如果您使用进程外会话存储,会话状态将仍然存在并且可供将来使用,无论 ASP.NET 辅助进程出现何种情况。如果该服务被中断,数据将被保留下来,并且在该服务恢复时自动进行检索。但是,如果状态提供程序服务停止或失败,数据将丢失。如果您希望应用程序具有强大的功能,请使用 SQLServer 模式,而不要使用 StateServer 模式。


    
        
    

您可以通过 sqlConnectionString 特性指定连接字符串。请注意,特性字符串必须包含用户 ID、密码和服务器名称。它不能包含 Database 和 Initial Catalog 之类的标记,因为此信息默认为固定名称。用户 ID 和密码可以替换为集成的安全设置。

如何创建数据库?ASP.NET 提供两对脚本来配置数据库环境。第一对脚本名为 InstallSqlState.sqlUninstallSqlState.sql,与会话状态 NT 服务位于同一个文件夹中。它们创建名为 ASPState 的数据库和几个存储的过程。但是,数据存储在 SQL Server 临时存储区域 TempDB 数据库中。这意味着,如果重新启动 SQL Server 计算机,会话数据将丢失。

要解决这一局限性,请使用第二对脚本。第二对脚本名为 InstallPersistSqlState.sqlUninstallPersistSqlState.sql。在这种情况下,将创建 ASPState 数据库,但是会在同一个数据库中创建数据表,而且这些数据表同样是持久的。为会话安装 SQL Server 支持时,还将创建一个作业,以删除会话状态数据库中过期的会话。该作业名为 ASPState_Job_DeleteExpiredSessions 并且一直运行。请注意,要使该作业正常进行,需要运行 SQLServerAgent 服务。

无论您选择哪种模式,为会话状态操作进行编码的方式都不会改变。您可以始终针对 Session 属性进行工作并像平常一样读取和写入值。所有行为上的差异都是在较低的抽象层上处理的。状态序列化或许是会话模式之间的最重要差异。

状态序列化和反序列化

使用进程内模式时,对象作为各自类的活动实例存储在会话状态中。如果未发生真正的序列化和反序列化,则表示您实际上可以在 Session 中存储您创建的任何对象(包括无法序列化的对象和 COM 对象),并且访问它们的开销也不会太高。如果您选择进程外状态提供程序,又是另外一种情况。

在进程外体系结构中,会话值将从本地存储介质(外部 AppDomain 数据库)复制到处理请求的 AppDomain 的内存中。需要使用序列化/反序列化图层完成该任务,并表示进程外状态提供程序的某项主要成本。这种情况对代码产生的主要影响是只能在会话词典中存储可序列化的对象。

根据所涉及的数据类型,ASP.NET 使用两种方法对数据进行序列化和反序列化。对于基本类型,ASP.NET 使用经过优化的内部序列化程序;对于其他类型(包括对象和用户定义的类),ASP.NET 使用 .NET 二进制格式化程序。基本类型包括字符串日期时间布尔值字节字符以及所有的数字类型。对于这些类型,使用量身制作的序列化程序要比使用默认的常用 .NET 二进制格式化程序更快。

经过优化的序列化程序没有公开发布,也没有以文档形式提供。它仅仅是二进制读取器/写入器,并且使用简单但有效的存储架构。该序列化程序使用 BinaryWriter 类写入一个字节表示类型,然后写入一个字节表示该类型对应的值。读取序列化的字节时,该类首先提取一个字节,检测要读取的数据类型,然后对 BinaryReader 类调用特定类型的 ReadXxx 方法。

请注意,布尔值和数字类型的大小是众所周知的,但对字符串并非如此。在基础数据流上,字符串始终带有一个固定长度的前缀(一次编写 7 位整数代码),读取器根据这一事实来确定字符串的正确大小。而日期值是通过只写入构成日期的标记总数来保存的。因此,要对会话执行序列化操作,日期应为 Int64 类型。

只要将包含的类标记为可序列化的类,便可以使用 BinaryFormatter 类对更复杂的对象(以及自定义对象)执行序列化操作。所有非基本类型都采用相同的类型 ID 进行标识并与基本类型存储在同一个数据流中。总之,序列化操作会导致性能下降 15% 至 25%。但请注意,这是基于假定使用基本类型所进行的粗略估计。使用的类型越复杂,开销越大。

如果不大量使用基本类型,很难实现有效的会话数据存储。因此,至少在理论上,使用三个会话槽保存对象的三个不同的字符串属性要比对整个对象进行序列化好。但是,如果要序列化的对象包含 100 个属性,那该怎么办呢?是要使用 100 个槽,还是只使用一个槽?在许多情况下,更好的方法是将复杂的类型转换为多个简单的类型。这种方法基于类型转换器。“类型转换器”是一种轻便的序列化程序,它以字符串集合的形式返回类型的关键属性。类型转换器是使用特性与基类绑定在一起的外部类。由类型编写者决定保存哪些属性以及如何保存。类型转换器对于 ViewState 存储也有帮助,它代表的是比二进制格式化程序更有效的会话存储方法。

会话的生命周期

关于 ASP.NET 会话管理,重要的一点是,仅当将第一个项目添加到内存词典中时,会话状态对象的生命周期才开始。仅在执行如下代码片断后,才可以认为 ASP.NET 会话开始。

Session["MySlot"] = "Some data";

Session 词典通常包含 Object 类型,要向后读取数据,需要将返回的值转换为更具体的类型。

string data = (string) Session["MySlot"];

当页面将数据保存到 Session 中时,会将值加载到 HttpSessionState 类包含的特制的词典类中。完成当前处理的请求时,会将词典的内容加载到状态提供程序中。如果由于未通过编程方式将数据放入词典而导致会话状态为空,则不会将数据序列化到存储介质中,而且更重要的是,不会在 ASP.NET Cache、SQL Server 或 NT 状态服务中创建槽来跟踪当前会话。这是出于性能方面的原因,但会对处理会话 ID 的方式产生重要影响:将为每个请求生成一个新的会话 ID,直到将某些数据存储到会话词典中。

需要将会话状态与正在处理的请求连接时,HTTP 模块会检索会话 ID(如果它不是启动请求),并在配置的状态提供程序中寻找它。如果没有返回数据,HTTP 模块将为请求生成一个新的会话 ID。这可以很容易地通过以下页面进行测试:

<%@ Page Language="C#" Trace="true" %>


无论何时单击该按钮并返回页面,都将生成新的会话 ID,同时记录跟踪信息。

图 3:在没有将数据存储到会话词典中的应用程序中,为每个请求生成一个新的会话 ID。

Session_OnStart 事件的情况如何呢?也会为每个请求引发该事件吗?如果应用程序定义 Session_OnStart 处理程序,则会始终保存会话状态,即使会话状态为空。因此,对于第一个请求之后的所有请求来说,会话 ID 始终为常量。仅在确实必要时,才使用 Session_OnStart 处理程序。

如果会话超时或被放弃,下次访问无状态应用程序时,其会话 ID 不会发生改变。经过设计后,即使会话状态过期,会话 ID 也能持续到浏览器会话结束。也就是说,只要浏览器实例相同,就始终使用同一个会话 ID 表示多个会话。

Session_OnEnd 事件标志着会话的结束,并用于执行终止该会话所需的所有清除代码。但请注意,只有 InProc 模式支持该事件,也就是说,只有将会话数据存储在 ASP.NET 辅助进程中时才支持该事件。对于要引发的 Session_OnEnd 事件来说,必须首先存在会话状态,这意味着必须在该会话状态中存储一些数据,并且必须至少完成一个请求。

在 InProc 模式下,作为项目添加到缓存中的会话状态被赋予一个可变过期时间策略。可变过期时间表示如果某个项目在一定时间内没有使用,将被删除。在此期间处理的任何请求的过期时间都将被重置。会话状态项目的时间间隔被设置为会话超时。用来重置会话状态过期时间的技术非常简单和直观:会话 HTTP 模块只读取 ASP.NET Cache 中存储的会话状态项目。如果知道 ASP.NET Cache 对象的内部结构,该模块将进行计算以重新设置可变过期时间。因此,当缓存项目过期时,会话已超时。

过期的项目将自动从缓存中删除。状态会话模块作为此项目的过期时间策略的一部分,也代表了一个删除回调函数。缓存将自动调用删除函数,删除函数然后将引发 Session_OnEnd 事件。如果应用程序通过进程外组件来执行会话管理,则永远不会引发结束事件。

Cookieless 会话

每个活动 ASP.NET 会话都是使用仅由 URL 允许的字符组成的 120 位字符串标识的。会话 ID 是使用随机数生成器 (RNG) 加密提供程序生成的。该服务提供程序返回一个包含 15 个随机生成数的序列(15 字节 x 8 位 = 120 位)。随机数数组然后被映射到有效的 URL 字符并以字符串形式返回。

会话 ID 字符串被发送到浏览器,然后通过以下两种方式之一返回服务器应用程序:使用 Cookie(就像在传统 ASP 中一样)或经过修改的 URL。默认情况下,会话状态模块将在客户端创建 HTTP Cookie,但是可以使用嵌入会话 ID 字符串的修改后的 URL(特别是对于不支持 Cookie 的浏览器)。采用哪种方法取决于应用程序的 web.config 文件中所存储的配置设置。要配置会话设置,可以使用 区段和 Cookieless 特性。


默认情况下,Cookieless 特性为 false,表示使用了 Cookie。实际上,Cookie 只是 Web 页放在客户端硬盘上的一个文本文件。在 ASP.NET 中,Cookie 由 HttpCookie 类的一个实例来表示。通常,Cookie 包含名称、值集合和过期时间。Cookieless 特性被设置为 false 时,会话状态模块实际上将创建一个名为 ASP.NET_SessionId 的 Cookie 并将会话 ID 存储在其中。下面的伪代码显示了创建 Cookie 的过程:

HttpCookie sessionCookie;
sessionCookie = new HttpCookie("ASP.NET_SessionId", sessionID);
sessionCookie.Path = "/";

会话 Cookie 的过期时间很短,在每个请求成功后更新过期时间。Cookie 的 Expires 属性表示 Cookie 在客户端的过期时间。如果未显式设置会话 Cookie,Expires 属性将默认为 DateTime.MinValue,即 .NET Framework 允许的最小时间单位。

要禁用会话 Cookie,请在配置文件中将 Cookieless 特性设置为 true,如下所示:


    
        
    

此时,假设您请求以下 URL 处的页面:

http://www.contoso.com/sample.aspx

浏览器地址栏中实际显示的内容会有所不同,现在包含会话 ID,如下所示:

http://www.contoso.com/(5ylg0455mrvws1uz5mmaau45)/sample.aspx

实例化会话状态 HTTP 模块时,该模块将检查 Cookieless 特性的值。如果为 true,则将请求重定向 (HTTP 302) 到经过修改的包含会话 ID(紧跟在页面名称前)的虚拟 URL。再次处理请求时,请求中会包含该会话 ID。如果请求启动新的会话,HTTP 模块将生成新的会话 ID,然后重定向该请求。如果回传请求,则会话 ID 已经存在,因为回传使用相对 URL。

使用 Cookieless 会话的缺点是,如果调用绝对 URL,将丢失会话状态。使用 Cookie 时,您可以清除地址栏,转至其他应用程序,然后返回上一个应用程序并检索相同的会话值。如果在禁用会话 Cookie 时执行此操作,将丢失会话数据。例如,以下代码将打断该会话:

Click

如果需要使用绝对 URL,请通过一些小技巧手动将会话 ID 添加到 URL 中。您可以对 HttpResponse 类调用 ApplyAppPathModifier 方法。

 >Click

ApplyAppPathModifier 方法将使用表示 URL 的字符串,并返回嵌入会话信息的绝对 URL。例如,需要从 HTTP 页面重定向到 HTTPS 页面时,此技巧特别有用。

小结

会话状态最初由传统的 ASP 引入,它是基于词典的 API,使开发人员能够存储会话期间的自定义数据。在 ASP.NET 中,会话状态支持以下两种主要功能:Cookieless 会话 ID 存储和传输,以及会话数据实际存储的状态提供程序。为实现这两种新功能,ASP.NET 利用 HTTP 模块控制会话状态与正在处理的请求上下文之间的绑定。

在传统的 ASP 中,使用会话状态就是指使用 Cookie。在 ASP.NET 中已不再如此,因为可以使用 Cookieless 架构。借助 HTTP 模块的力量,可以分解请求的 URL 以使其包含会话 ID,然后将其重定向。接下来,HTTP 模块会从该 URL 中提取会话 ID 并使用它检索任何存储的状态。

会话的物理状态可以存储在三个位置:进程内内存、进程外内存和 SQL Server 表。数据必须经过序列化/反序列化处理,才能供应用程序使用。HTTP 模块会在请求开始时将会话值从提供程序复制到应用程序的内存中。请求完成后,修改后的状态将返回提供程序。这种数据通信会对性能产生不同程度的不利影响,但是会大大增强可靠性和稳定性,也使对 Web Farm 和 Web Garden 体系结构的支持更容易实现。

作者简介

Dino Esposito 是一位来自意大利罗马的培训教师和顾问。作为 Wintellect 小组的成员,Dino 专门研究 ASP.NET 和 ADO.NET,主要在欧洲和美国从事教学和咨询工作。此外,Dino 还负责管理 Wintellect 的 ADO.NET 课件,并为 MSDN Magazine 的“Cutting Edge”专栏撰写文章。



认识ASP.NET会话状态
  一、会话状态的功能

  HTTP 是一个无状态的协议,所以它不自动指示一个请求序列是否都来自相同的客户端,甚至不指示单个浏览器实例是否仍活跃地查看某个页或站点。而使用ASP.NET内置的会话状态功能,可以使用我们做到

  1、对从单个浏览器客户端到服务器上逻辑应用程序会话的请求进行自动识别和分类。

  2、将会话范围的数据存储在服务器上以供跨多个浏览器请求使用。

  3、引发适当的可在应用程序代码中处理的会话生存期管理事件(Session_OnStart、Session_OnEnd 等)

  二、会话状态的标识

  在创建会话时,服务器会为每一个会话生成一个单独的标识。该标识用 120 位的 SessionID 字符串表示,该字符串只包含 URL 中所允许使用的 ASCII 字符。SessionID 值是使用保证唯一性和随机性的算法生成的,其中保证唯一性的目的是确保会话不冲突,保证随机性的目的是确保怀有恶意的用户不能使用新的 SessionID 来计算现有会话的 SessionID。

  三、会话状态的存储方式

  会话状态有三种存储方式

  1、进程内会话状态模式(Inproc):当我们新建一个WEB程序后默认的采用的进程内会话状态模式,这也是大家所普遍采用的模式。在这种模式下会话状态存储在本地的 ASP.NET 辅助进程中,可以说到目前为止,进程内会话状态模式可能是最快的访问选项。但会话中存储的数据越多,Web 服务器所消耗的内存就越多,这样会潜在地增加性能降低的风险。

  2、.NET 状态服务器模式(StateServer):会话状态存储在远程进程中(例如,名为 aspnet_state.exe的 indows NT 服务中)

  3、SQL 模式(SQLServer):会话状态存储到由 SQL Server 管理的专用数据库表中。

  .NET 状态服务器模式和SQL 模式都可以称为进程外会话模式,当储存数据时,需要将数据序列化储存到外部储备库,当读取和数据时,需要将数据反序列化,复制到本地会话词典中,所以请求导致性能下降了 15%(进程外)到 25% (SQL Server)。注意这只是一种粗略的估计。但是在进程外存储方案中,会话状态存活的时间较长,使应用程序的功能更强大,因为它可以防止 Microsoft? Internet 信息服务 (IIS) 和ASP.NET 失败。通过将会话状态与应用程序相分离,您还可以更容易地将现有应用程序扩展到 Web Farm 和 Web Garden 体系结构中。另外,会话状态存储在外部进程中,从根本上消除了由于进程循环而导致的周期性数据丢失的风险。

  四、会话状态的配置

  会话状态的配置是通过设置Web.config文件的<sessionState>节来实现的。下面介绍一下三种会话状态的具体配置方法

  1、进程内模式

  进程内模式是默认的会话状态模式。若要使用进程内模式,请将 <sessionState> 元素的 mode 属性设置为 Inproc。

  下面显示了进程内模式的一个配置设置示例。

<configuration>
<system.web>
<sessionState mode="Inproc"
cookieless="false"
timeout="20"/>
</sessionState>
</system.web>
</configuration>


  2、状态服务器模式

  若要使用状态服务器,必须首先确保 ASP.NET 状态服务运行在用于会话存储的远程服务器上。此服务与ASP.NET 和 Visual Studio .NET 一起安装在以下位置:

systemroot/Microsoft.NET/Framework/versionNumber/aspnet_state.exe

  然后,在应用程序的 Web.config 文件中,将 <sessionState> 元素的 mode 属性设置为 StateServer。最后,将 connectionString 属性设置为 tcpip=serverName:portNumber。

  下面是状态服务器模式的一个配置设置示例。

<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="tcpip=dataserver:42424"
cookieless="false"
timeout="20"/>
</sessionState>
</system.web>


  3、SQL Server 模式

  若要使用 SQL Server,首先在将存储会话状态的 SQL Server 计算机上,运行 InstallSqlState.sql 或 InstallPersistSqlState.sql。两个脚本均创建一个名为 ASPState 的数据库,它包含若干存储过程。

  两个脚本间的差异在于放置 ASPStateTempApplications 和 ASPStateTempSessions 表的位置。InstallSqlState.sql 脚本将这些表添加到 TempDB 数据库,该数据库在计算机重新启动时将丢失数据。相反,InstallPersistSqlState.sql 脚本将这些表添加到 ASPState 数据库,该数据库允许在计算机重新启动时保留会话数据。

  默认情况下,两个脚本文件均安装在下面的位置:

systemroot/Microsoft.NET/Framework/versionNumber

  然后,在应用程序的 Web.config 文件中,将 <sessionState> 元素的 mode 属性设置为 SQLServer。最后,将 sqlConnectionString 属性设置为 Integrated Security=SSPI;data source=serverName;。

  下面显示了 SQL Server 模式的一个配置设置示例。

<configuration>
<system.web>
<sessionState mode="SQLServer"
sqlConnectionString=" Integrated Security=SSPI;data source=dataserver;"
cookieless="false"
timeout="20"/>
</sessionState>
</system.web>
</configuration>


  在 SQL Server 模式中,也可以将会话状态配置为在故障转移群集中工作。故障转移群集是两个或更多相同的冗余 Web 服务器,它们将会话数据存储在一台单独的计算机上的 SQL Server 数据库中。如果一个 Web 服务器出现故障,群集中的另一个服务器会接管它的工作,为请求提供服务,会话数据不会丢失。

  若要配置故障转移群集,请将 Web 服务器的 Web.config 文件中的 <machinekey> 元素设置为相同的值。

  然后将 Web 服务器的 SQL 连接字符串设置为指向计算机上存储会话数据的 SQL Server 数据库。

  五、会话状态的访问

  你可以直接通过Session集合来实现对会话状态的访问。为了与 ASP 的早期版本兼容,还可以通过应用程序对象上的 Session.Contents 属性来实现对会话状态的访问。

  下面的示例显示在第一个网页将两个值写入Session集合,然后再在第二个网页读取Session集合。注:此处省略了页代码。

  第一个网页,将值写入Session集合

dim name as string = "a"
dim id as integer = "1"
session("name") = name
session("id") = id
第二个网页,从Session集合获取值
dim name as string = session("name")
dim id as integer = session("id")
'获取获取会话状态集合中的项数
dim i as integer = session.count


  注意,在进程内模式,未发生真正的序列化和反序列化,所以对象作为各自类的活动实例存储在会话状态中。

  而在进程外会话模式,因为使用了序列化和反序列化,所以你要根据情况对数据类型进行转换。

  如对日期值执行序列化操作,日期应为 Int64 类型。

  六、会话生存期管理事件

  会话生存期管理事件有两个Session_OnStart事件和Session_OnEnd事件,你可以在Global.asax.vb文件中对它们进行设置

  1、Session_OnStart事件

  当从单个浏览器客户端连接到服务器上时,就会触发Session_OnStart事件,它标志着会话的开始,在此后的浏览过程中,将不在触发该事件,除非此次会话超时或被放弃。Session_OnStart 事件是设置会话期变量的最佳时机,因为在访问任何页之前都会先设置它们。

  示例:下面的示例是比较常用到的统计在线人数的Session_OnStart 事件代码:

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
'当事件发生时,将在线用户的人数加1
Application("usercount") = Application("usercount") + 1
End Sub


  2、Session_OnEnd事件

  Session_OnEnd 事件在会话被放弃或超时发生,它标志着事件的结束。但请注意,只有 InProc 模式支持该事件。你可以通过Web.config文件的<sessionState>节的timeout属性来指定超时时限,如果用户在该超时时限之内(以分钟为单位,默认是20分
钟)不刷新或请求网页,则该会话将终止。可以利用Session_OnEnd 事件做一些清理工作。

  示例:下面的示例是比较常用到的统计在线人数的Session_OnEnd 事件代码:

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
Application("usercount") = Application("usercount") - 1
End Sub

你可能感兴趣的:(asp.net,sql,server,session,存储,sqlserver,服务器)