ASP.NET运行时模型的源码解析

时间:2022-05-29 10:32:38

摘要:是微软公司推出的基于DotNet平台的网络开发技术,对B/S模式应用的开发提供了强大的支持,该文从源代码级别进入底层,分析运行时模型,揭开应用程序运行的幕后细节。

关键词:;DotNet平台;网络开发;B/S模式应用;运行时模型

中图分类号:TP311文献标识码:A文章编号:1009-3044(2008)33-1415-04

SourceCode Analyzing for Runtime Model

ZHANG Jia-qing

(Fujian Institude of Economics Management, Fuzhou 350002, China)

Abstract: Based on DotNet platform, Microsoft Proposed the network developing technology called which provides strong support to the development of B/S pattern application programs. This paper enters into the bottom of from the level of source code so as to analyze the runtime model of , so that running details behind the curtain of application programs are uncovered.

Key words: ; DotNet platform; network developing; B/S pattern application; runtime model

1 引言

运行时模型指的是 框架在接收到客户端请求,到获取合适的HttpHandler接管请求之间,以及从HttpHandlers生成处理结果到发送处理结果给客户端之间, 框架所完成的一系列工作和处理逻辑[1]。本文将通过源代码级别的分析,深入讨论 2.0运行时模型,解析应用程序运行时模型的工作原理。

2 ISAPI接口标准与IIS服务器的可扩展性

IIS服务器是部署应用的标准Web服务器,由于IIS服务器在设计时引入了开放的ISAPI接口标准,具备极高的可扩展性,在核心组件不变的情况下可灵活支持不同类型不同版本的应用,例如IIS 5.1版能够同时支持 1.0、1.1、2.0,甚至可以支持部署基于DotNetFramework 3.5的应用程序。ISAPI的全称是:Internet Server Application Programming Interface,即Internet服务器应用编程接口,它为开发人员提供了强大的可编程能力,只要按照标准接口开发不同类型Web应用程序的ISAPI扩展程序,就能实现对IIS功能上的扩展,从而使IIS可以处理不同类型的客户端请求,例如让IIS处理Perl或PHP应用程序[2]。

IIS管理器提供了应用程序配置功能,可以对不同的客户端请求配置不同的ISAPI扩展程序。ISAPI扩展程序通常以DLL的形式存在,可以被IIS加载并调用。在2.0下,对应于.aspx的应用程序,其ISAPI扩展程序默认情况下位于C:\WINDOWS\\Framework\v2.0.50727\目录下,以aspnet_isapi.dll(下文简称aspnet_isapi)文件的形式存在[3]。

有了ISAPI扩展,IIS服务器就可以根据客户端请求的资源扩展名,来决定应由哪个ISAPI扩展来处理客户端请求,然后就可以将请求转发给合适的ISAPI扩展。例如当访问资源的扩展名为.aspx、.ascx、.ashx 和 .asmx时,IIS服务器会自动将请求转发给缺省情况下位于C:\WINDOWS\\Framework\v2.0.50727\目录下aspnet_isapi.dll进行处理[4],这个动态库就是一个ISAPI扩展程序,负责处理应用程序的请求。

3 的后台辅助进程aspnet_wp.exe

实际上客户发起的请求最终要由aspnet_isapi传递给它背后的aspnet_wp.exe去处理,.Net平台将其称为 Worker Process(下文简称WP),该文件位于.NET framework安装目录下,与aspnet_isapi.dll所在位置相同。

当aspnet_isapi接收到IIS转发的请求后,会将请求放入队列,并根据实际情况分配请求处理任务给WP进程,一旦请求被转送给WP进程,WP进程便会通知aspnet_isapi,请求正在被处理。这个通知的过程是通过同步I/0完成的,这么实现目的是为了保证处理过程的完整性,因为只有当请求在aspnet_isapi内部被标记为“executing”后,WP才会真正开始处理该请求。此后请求便在WP的上下文环境中执行,当执行结束后,处理结果会通过一个异步的开放管道回送给aspnet_isapi,这时请求的状态会被更新为“Done”,接着请求就会从队列中清除。如果WP进程崩溃,所有正在处理中的请求,都将维持“executing”状态一段时间,等到aspnet_isapi检测到WP进程死掉后,会自动丢弃所有的请求,并释放已经分配的资源[5]。其过程如图1所示。

WP会分析每一个请求的信息,解析出其中的虚拟目录信息,并检查该虚拟目录对应的AppDomain是否已经存在,如果不存在则创建一个新的AppDomain,然后使用它,否则直接重用已经建立的AppDomain对象。这里的AppDomain指的是.NET中引入的应用程序域的概念,它可以理解为一个进程或一个边界,或一个容器,它是应用程序的执行环境,.NET下所有的应用程序都运行在AppDomain中,每一个应用程序(IIS中的站点或者虚拟目录)都会有一个AppDomain与之对应[6],它保存了Applcation对象、Cache等全局变量。

4 请求在运行时中的处理,步骤一:ISAPIRuntime->HttpRuntime

WP接收到aspnet_isapi转发的请求后,就将请求转送给指定虚拟目录对应的AppDomain中的ISAPIRuntime对象,由它完成对aspnet_isapi封装后的请求包的解析工作,笔者使用Lutz Roeder's .NET Reflector工具查看System.Web.Dll,通过分析ISAPIRuntime的源码发现,客户的请求首先会被该类的ProcessRequest方法处理,该方法的部分代码如下:

ISAPIWorkerRequest wr = null;

try{

bool useOOP = iWRType == 1;

wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);

wr.Initialize();

……

if (……){

HttpRuntime.ProcessRequestNoDemand(wr);

return 0;

}

……

}

可以看出在ProcessRequest方法中,主要通过调用一些非托管代码生成HttpWorkerRequest对象,在上述代码中,对应于wr对象(ISAPIWorkerRequest类型继承自HttpWorkerRequest类),该对象包含当前请求的所有信息,然后ISAPIRuntime调用HttpRuntime的ProcessRequestNoDemand方法传递创建好的HttpWorkerRequest对象,这个方法的代码如下:

internal static void ProcessRequestNoDemand(HttpWorkerRequest wr){

RequestQueue queue = _theRuntime._requestQueue;

if (queue != null){

wr = queue.GetRequestToExecute(wr);

}

if (wr != null){

CalculateWaitTimeAndUpdatePerfCounter(wr);

wr.ResetStartTime();

ProcessRequestNow(wr);

}

}

该方法先从请求队列中取出一个请求,然后更新请求的引用计数器等信息,接着就让ProcessRequestNow方法处理请求,这个方法的代码如下:

internal static void ProcessRequestNow(HttpWorkerRequest wr){

_theRuntime.ProcessRequestInternal(wr);

}

追踪分析源码可以发现_theRuntime对象是在HttpRuntime内部定义的,其类型就是HttpRuntime自身,它在HttpRuntime的静态构造函数中被初始化。

5 请求在运行时中的处理,步骤二:HttpRuntime.ProcessRequestInternal()

接下来我们重点分析HttpRuntime类ProcessRequestInternal方法的运作细节!使用Reflector工具追踪至ProcessRequestInternal方法的内部:

HttpContext context;

context = new HttpContext(wr, false);

……

this.EnsureFirstRequestInit(context);

context.Response.InitResponseWriter();

……

IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);

……

IHttpAsyncHandler handler2 = (IHttpAsyncHandler)applicationInstance;

context.AsyncAppHandler = handler2;

handler2.BeginProcessRequest(context,this._handlerCompletionCallback, context);

……

通过分析该方法的源码,发现它主要完成如下处理:

1) 根据HttpWorkerRequest对象初始化HttpContext对象,它包含了request、response等属性,在编程中经常会用到这些重要的属性来完成特定的任务。

2) 调用EnsureFirstRequestInit方法完成第一次请求的初始化工作,该方法锁定全局变量_beforeFirstRequest,然后调用FirstRequestInit(context)完成应用程序配置文件的加载、初始化请求队列、装载Bin目录下的所有程序集等工作,然后更新_beforeFirstRequest为false。

3) 执行InitResponseWriter创建HttpWriter对象,用于写入处理结果返回信息。

4) 调用HttpApplicationFactory类的GetApplicationInstance方法来生成IHttpHandler(这里生成的是一个默认的HttpApplication对象,HttpApplication实现了IHttpHandler接口)。

5) 调用HttpApplication对象(它同时实现了IHttpAsyncHandler接口)的BeginProcessRequest方法执行客户请求。

需要注意的是,笔者在跟踪至HttpApplicationFactory类的GetApplicationInstance方法内部时发现它最终通过调用该类的GetNormalApplicationInstance方法获取HttpApplication实例:

return _theApplicationFactory.GetNormalApplicationInstance(context);

再深入一步,进入GetNormalApplicationInstance方法内部,我们终于看到HttpApplication对象是如何被创建和初始化的:

HttpApplication application = null;

……

application = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType);

using (new ApplicationImpersonationContext()){

application.InitInternal(context, this._state, this._eventHandlerMethods);

}

我们发现HttpApplication类提供了一个名为InitInternal的方法,调用方通过它来完成HttpApplication实例的初始化工作,在这个方法的内部,有如下代码:

……

this.InitModules();

……

this.HookupEventHandlersForApplicationAndModules(handlers);

……

this._stepManager = new ApplicationStepManager(this);

this._stepManager.BuildSteps(this._resumeStepsWaitCallback);

……

可以看到在HttpApplication对象初始化时,首先会自动调用自身的InitModules方法来加载在web.config文件中配置的所有HttpModule模块。接着HookupEventHandlersForApplicationAndModules方法被调用,这个方法完成global.asax文件中配置的HttpApplication或HttpModule事件的绑定。最后ApplicationStepManager对象的BuildSteps方法被调用,完成HttpApplication事件的绑定,这个方法很重要,它将创建各种HttpApplication.IExecutionStep对象并保存到一个数组列表中,以便在BeginProcessRequest方法内部调用ResumeSteps方法依次执行这些对象的Execute()方法,完成各种处理。

上一篇:电信用户呼叫行为的模糊聚类研究 下一篇:浅议中职数学教学的思想教育