首页 > 编程 > .NET > 正文

ASP.NET 中 Session 实现原理浅析 [1] 会话的建立流程

2024-07-10 12:56:21
字体:
来源:转载
供稿:网友


http 协议之所以能够获得如此大的成功,其设计实现的简洁性和无状态连接的高效率是很重要的原因。而为了在无状态的 http 请求和有状态的客户端操作之间达到平衡,产生了服务器端会话 (session) 的概念。客户端在连接到服务器后,就由 web 服务器产生并维护一个客户端的会话;当客户端通过无状态 http 协议再次连接到服务器时,服务器根据客户端提交的某种凭据,如 cookie 或 url 参数,将客户关联到某个会话上。这种思路在各种开发语言和开发环境中大量得到应用。
在 asp.net 中,web 应用程序和会话状态被分别进行维护,通过 httpapplication 和 httpsessionstate 分离 web 应用程序与会话的功能。应用程序层逻辑在 global.asax 文件中实现,运行时编译成 system.web.httpapplication 的实例;会话则作为单独的 system.web.sessionstate.httpsessionstate 实例,由服务器统一为每个用户会话维护,通过 asp.net 页面编译成的 system.web.ui.page 对象子类的 session 属性访问。关于 asp.net 中不同层次关系可参考我以前的一篇文章《.net 1.1中预编译asp.net页面实现原理浅析 [1] 自动预编译机制浅析》,以下简称【文1】。

asp.net 在处理客户端请求时,首先将根据客户端环境,生成一个 system.web.httpcontext 对象,并将此对象作为执行上下文传递给后面的页面执行代码。
在【文1】的分析中我们可以看到,httpruntime 在处理页面请求之前,根据 httpworkerrequest 中给出的环境,构造 httpcontext 对象,并以次对象作为参数从应用程序池中获取可用应用程序。简要代码如下:
以下内容为程序代码:

private void httpruntime.processrequestinternal(httpworkerrequest wr)
{
// 构造 http 调用上下文对象
httpcontext ctxt = new httpcontext(wr, 0);

//...

// 获取当前 web 应用程序实例
ihttphandler handler = httpapplicationfactory.getapplicationinstance(ctxt);

// 调用 handler 实际处理页面请求
}




httpapplicationfactory 工厂内部维护了一个可用的应用程序实例缓冲池,用户降低应用程序对象构造的负荷。
如果池中没有可用的应用程序对象实例,此对象工厂最终会调用 system.web.httpruntime.createnonpublicinstance 方法构造新的应用程序实例,并调用其 initinternal 方法初始化。详细步骤分析见【文1】
以下内容为程序代码:

internal static ihttphandler httpapplicationfactory.getapplicationinstance(httpcontext ctxt)
{
// 处理定制应用程序
//...

// 处理调试请求
//...

// 判断是否需要初始化当前 httpapplicationfactory 实例
//...

// 获取 web 应用程序实例
return httpapplicationfactory._theapplicationfactory.getnormalapplicationinstance(ctxt);
}

private httpapplication httpapplicationfactory.getnormalapplicationinstance(httpcontext context)
{
httpapplication app = null;

// 尝试从已施放的 web 应用程序实例队列中获取
//...

if(app == null)
{
// 构造新的 web 应用程序实例
app = (httpapplication)system.web.httpruntime.createnonpublicinstance(this._theapplicationtype);

// 初始化 web 应用程序实例
app.initinternal(context, this._state, this._eventhandlermethods);
}

return app;
}



这里的 system.web.httpapplication.initinternal 函数完成对应用程序对象的初始化工作,包括调用 httpapplication.initmodules 函数初始化 http 模块(后面将详细介绍),并将作为参数传入的 httpcontext 实例保存到 httpapplication._context 字段中。而此 http 上下文对象将被后面用于获取会话对象。
以下内容为程序代码:

public class httpapplication : ...
{
private httpcontext _context;
private httpsessionstate _session;

public httpsessionstate session
{
get
{
httpsessionstate state = null;
if (this._session != null)
{
state = this._session;
}
else if (this._context != null)
{
state = this._context.session;
}
if (state == null)
{
throw new httpexception(httpruntime.formatresourcestring("session_not_available"[img]/images/wink.gif[/img]);
}
return state;
}
}
}



而在 asp.net 页面中获取会话的方法也是类似,都是通过 httpcontext 来完成的。
以下内容为程序代码:

public class page : ...
{
private httpsessionstate _session;
private bool _sessionretrieved;

public virtual httpsessionstate session
{
get
{
if (!this._sessionretrieved)
{
this._sessionretrieved = true;
try
{
this._session = this.context.session;
}
catch (exception)
{
}
}
if (this._session == null)
{
throw new httpexception(httpruntime.formatresourcestring("session_not_enabled"[img]/images/wink.gif[/img]);
}
return this._session;
}
}
}



在 httpcontext 中,实际上是通过一个哈希表保存诸如会话对象之类信息的
以下内容为程序代码:

public sealed class httpcontext : ...
{
private hashtable _items;

public idictionary items
{
get
{
if (this._items == null)
{
this._items = new hashtable();
}
return this._items;
}
}

public httpsessionstate session
{
get
{
return ((httpsessionstate) this.items["aspsession"]);
}
}
}



而 httpcontext.session 所访问的又是哪儿来的呢?这就又需要回到我们前面提及的 httpapplication.initmodules 函数。

在 .net 安装目录 config 子目录下的 machine.config 定义了全局性的配置信息,而 httpapplication 就是使用其中 system.web 一节的配置信息进行初始化的。
以下内容为程序代码:

<system.web>
<httpmodules>
<add name="outputcache" type="system.web.caching.outputcachemodule" />
<add name="session" type="system.web.sessionstate.sessionstatemodule" />
<add name="windowsauthentication" type="system.web.security.windowsauthenticationmodule" />
<add name="formsauthentication" type="system.web.security.formsauthenticationmodule" />
<add name="passportauthentication" type="system.web.security.passportauthenticationmodule" />
<add name="urlauthorization" type="system.web.security.urlauthorizationmodule" />
<add name="fileauthorization" type="system.web.security.fileauthorizationmodule" />
<add name="errorhandlermodule" type="system.web.mobile.errorhandlermodule, system.web.mobile, version=1.0.5000.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a" />
</httpmodules>
</system.web>



httpmodules 节点指定了 httpapplication 需要初始化的模块列表,而在前面提到的 httpapplication.initmodules 函数正式根据此列表进行初始化的
以下内容为程序代码:

private void httpapplication.initmodules()
{
httpmodulesconfiguration cfgmodules = ((httpmodulesconfiguration) httpcontext.getappconfig("system.web/httpmodules"[img]/images/wink.gif[/img]);

if (cfgmodules == null)
{
throw new httpexception(httpruntime.formatresourcestring("missing_modules_config"[img]/images/wink.gif[/img]);
}
_modulecollection = cfgmodules.createmodules();

for(int i = 0; i < _modulecollection.count; i++)
{
_modulecollection[i].init(this);
}

globalizationconfig cfgglobal = ((globalizationconfig) httpcontext.getappconfig("system.web/globalization"[img]/images/wink.gif[/img]);
if (cfgglobal != null)
{
_applevelculture = cfgglobal.culture;
_appleveluiculture = cfgglobal.uiculture;
}
}



session 节点对于的 system.web.sessionstate.sessionstatemodule 对象将被 httpmodulesconfiguration.createmodules 方法构造,并调用其 init 函数初始化。sessionstatemodule 类实际上就是负责管理并创建会话,用户完全可以自行创建一个实现 ihttpmodule 接口的类,实现会话的控制,如实现支持集群的状态同步等等。
sessionstatemodule.init 方法主要负责 machine.config 文件中的 sessionstate 配置,调用 sessionstatemodule.initmodulefromconfig 方法建立相应的会话管理器。
以下内容为程序代码:

<system.web>
<sessionstate mode="inproc"
stateconnectionstring="tcpip=127.0.0.1:42424"
statenetworktimeout="10"
sqlconnectionstring="data source=127.0.0.1;integrated security=sspi"
cookieless="false"
timeout="20" />
</system.web>



sessionstate 的使用方法请参加 msdn 中相关介绍和 info: asp.net state management overview。关于不同 mode 的会话管理的话题我们后面再讨论,先继续来看会话的建立过程。

在从 machine.config 文件中读取配置信息后,initmodulefromconfig 方法会向 httpapplication 实例注册几个会话管理事件处理函数,负责在应用程序合适的情况下维护会话状态。
以下内容为程序代码:

private void sessionstatemodule.initmodulefromconfig(httpapplication app,
sessionstatesectionhandler.config config, bool configinit)
{
// 处理不使用 cookie 的情况
//...

app.addonacquirerequeststateasync(new begineventhandler(this.beginacquirestate),
new endeventhandler(this.endacquirestate));
app.releaserequeststate += new eventhandler(this.onreleasestate);
app.endrequest += new eventhandler(this.onendrequest);

// 创建会话管理器
//...
}



beginacquirestate 和 endacquirestate 作为一个异步处理器注册到 httpapplication._acquirerequeststateeventhandlerasync 字段上;onreleasestate 则负责在合适的时候清理会话状态;onendrequest 则是 onreleasestate 的一个包装,负责较为复杂的请求结束处理。前面提到的 httpapplication.initinternal 函数,在完成了初始化工作后,会将上述这些事件处理器,加入到一个执行队列中,由应用程序在合适的时候,使用流水线机制进行调用,最大化处理效率。有关 asp.net 中流水线事件模型的相关介绍,请参考 http pipelines
securely implement request processing, filtering, and content redirection with http pipelines in asp.net 一文中 the pipeline event model 小节,有机会我在写文章详细分析。

知道了会话建立的调用流程再来看会话的实现就比较简单了,sessionstatemodule.beginacquirestate 被 httpapplication 实例在合适的时候调用,处理各种会话的复杂情况后,使用 sessionstatemodule.completeacquirestate 函数完成实际的会话建立工作,并将封装会话的 httpsessionstate 对象以 "aspsession" 为 key 加入到 httpcontext 的哈希表中,也就是前面提到的 httpcontext.context 的由来。而 sessionstatemodule.onreleasestate 则从 httpcontext 中删除 "aspsession" 为 key 的 httpsessionstate 对象,并对会话管理器进行同步工作。

至此,asp.net 中的会话建立流程大概就分析完毕了,下一小节将进一步展开分析多种不同会话管理器的实现原理与应用。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表