简介
不管使用哪种底层平台,可靠性和性能都是对所有web应用程序的主要要求,尽管从某种意义上讲,这两个要求是相互矛盾的。例如,要构建更可靠、更健壮的应用程序,可能需要将web服务器与具体的应用程序分离,使应用程序在进程外工作。但是,如果在不同于web服务器进程的内存环境中工作,应用程序将变慢。因此,需要采取合理的措施,以确保进程外代码尽可能快地运行。
在构建microsoft?asp.net运行时环境时,依据的设计原则即:充分考虑可靠性和性能。得到的asp.net进程模型包含了两个系统元素-一个存在于web服务器进程中的进程内连接器,一个外部的辅助进程。另外,asp.net运行时结构的可伸缩能力很强,可以自动使用多处理器硬件中任意选定的处理器。这种模式被称为“webgarden”,它可以使多个辅助进程同时运行,而且各个进程均在独立的处理器中。
高度概括起来,asp.net运行时具有三大属性:
应用程序和asp.net辅助进程之间完全分离。提供服务的辅助进程的寿命决不会影响应用程序的寿命。换句话说,当应用程序启动并处于运行状态时,辅助进程可以随时终止。
尽管asp.net应用程序从不在web服务器内采用进程内的方式运行,但大多数情况下,其总体性能仍接近于进程内应用程序的性能。
为webgarden体系结构提供了内置的和可配置的支持。只要简单检查一下配置文件中的设置,辅助进程就可以克隆自己,以利用所有与进程密切相关的cpu。因此,在大多数情况下,您在具备多处理器的计算机中获得的可缩放性将呈线性增长的趋势。(本文后面将详细介绍此内容。)
本文将介绍asp.net运行时环境的组成元素,然后一步一步地讲述从url请求变为纯html文本的“漫长而曲折”的过程。
除非另有说明,否则以下介绍中均指asp.net的默认进程模型,即microsoft?internetinformationservices(iis)5.x中唯一的模型。
asp.net结构的组件
执行asp.net应用程序需要宿主web服务器的支持。在microsoft?windows?的server平台中,web服务器由名为inetinfo.exe的iis可执行文件表示。windows2000及以上版本的操作系统本身均提供了web服务器。但需要注意,在microsoft?windowsserver™2003中,并未默认安装iis和asp.net,必须通过单击“控制面板”中的“添加或删除程序”小程序将其添加到系统中。
iis是一个未托管的可执行程序,它提供了一个基于isapi扩展模块和筛选器模块的可扩展模型。通过编写此类模块,开发人员可以直接管理对特定资源类型的请求,并在各个预定义的步骤中接收当前请求。扩展和筛选器是一些dll,可以导出一些具有已知名称和签名的函数。这些插件组件是在iis配置数据库中注册并配置的。
只有少数几种被客户端请求的资源类型由iis直接处理。例如,对html页面、文本文件、jpeg和gif图像的传入请求由iis处理。对activeserverpage(*.asp)文件的请求通过调用名为asp.dll的asp专用扩展模块进行解析。同样,对asp.net资源(例如,*.aspx、*.asmx、*.ashx)的请求将传递到asp.netisapi扩展。该系统组件是一个名为aspnet_isapi.dll的win32dll。asp.net扩展可以处理多种资源类型,包括web服务和http处理程序调用。
asp.netisapi扩展是一个win32dll,未集成托管代码。它是接收和分派对各种asp.net资源的请求的控制中心。按照设计,该模块存在于iis进程中,在具有管理员权限的system帐户下运行。开发人员和系统管理员不能修改此帐户。asp.netisapi扩展负责调用asp.net辅助进程(aspnet_wp.exe),而该进程又负责控制请求的执行。除了对请求进行安排以外,asp.netisapi还监视辅助进程的运行情况,并在性能降低到一定程度时将进程取消。
辅助进程是一小段win32shell代码,集成了公共语言运行库(clr)并运行托管代码。它负责处理对aspx、asmx和ashx资源的请求。一般来说,此进程在一台给定的计算机中只有一个实例。所有当前激活的asp.net应用程序均在其中运行,每个应用程序都位于一个独立的appdomain中。但是,如前所述,辅助进程支持webgarden模式,即进程的相同副本都运行在与进程密切相关的cpu中。(更多内容,请参阅本文后面的“webgarden模型”部分。)
isapi和辅助进程之间的通讯是使用一组命名管道进行的。命名管道是一种win32机制,用于跨进程边界传输数据。顾名思义,命名管道的工作方式与管道相似:在一端输入数据,在另一端输出相同的数据。建立的管道既可以连接本地进程,也可以连接远程计算机上运行的进程。对于本地进程间通讯,管道是windows中的最有效、最灵活的工具。
为确保获得最优性能,aspnet_isapi使用异步命名管道来将请求转发给辅助进程并获得响应。另一方面,辅助进程在需要查询有关iis环境的信息(即服务器变量)时又使用同步管道。aspnet_isapi模块创建固定数量的命名管道,并使用重叠的操作以通过小的线程池处理同一时间进行的连接。当通过管道进行的数据交换操作结束后,完成例程将断开客户端,并重新使用管道实例为新的客户端服务。线程池和重叠操作均可以保证使asp.netisapi的性能达到令人满意的水平。但是,aspnet_isapi扩展决不会处理http请求。
asp.net请求的处理逻辑可以概括为以下步骤:
当请求到达时,iis检查资源类型并调用asp.netisapi扩展。如果启用了默认的进程模型,aspnet_isapi会将请求排队,并将请求分配给辅助进程。所有的请求数据都通过异步i/o发送。如果启用了iis6进程模型,请求将自动在辅助进程(w3wp.exe)中排队,此辅助进程用于处理应用程序所属的iis应用程序池。iis6辅助进程不了解asp.net和托管代码的任何情况,它只是处理*.aspx扩展并加载aspnet_isapi模块。当asp.netisapi在iis6进程模型中运行时,它的工作方式有所不同,仅在w3wp.exe辅助进程的上下文中加载clr。
收到请求后,asp.net辅助进程将通知asp.netisapi,它将为请求服务。通知通过同步i/o实现。之所以使用同步模型,是因为请求只有在isapi内部请求表中被标记为“executing”,辅助进程才能开始处理它。如果请求已经由特殊的辅助进程进行处理,则不能再将它指定到其他进程,除非原始进程已取消。
在辅助进程的上下文中执行请求。有时,辅助进程可能需要回调isapi以完成请求,也就是需要说枚举服务器变量。这种情况下,辅助进程将使用同步管道,因为这样可以保持请求处理逻辑的顺序。
完成后,响应被发送到打开了异步管道的aspnet_isapi。现在,请求的状态变为“done”,之后将从请求表中被删除。如果辅助进程崩溃,正在处理的所有请求仍将保持“executing”状态并持续一段时间。如果aspnet_isapi检测到辅助进程已取消,它将自动终止请求并释放所有相关的iis资源。
以上说明是指默认的asp.net进程模型,即在iis5.x中运行的工作模型。iis6(windowsserver2003提供)的默认工作方式对asp.net进程模型也有影响。当集成在iis6.0中时,asp.net1.1会自动调整自己的工作方式以适应宿主环境。这时,不再需要使用aspnet_wp辅助进程,machine.config文件中定义的某些配置参数也被忽略。从asp.net的角度来看,iis6的最大改变是有关请求的一切都在aspnet_isapi的控制之下,且都处在w3wp.exe辅助进程的上下文中。辅助进程的帐户是为web应用程序所属的应用程序池设置的帐户。默认情况下,该帐户是networkservice&#151,它是一个内置的弱帐户,在功能上与aspnet等价。
辅助进程受一个名为进程回收(recycling)的功能的控制。进程回收具有aspnet_isapi功能,当现有进程消耗的内存太多、响应太慢或挂起时可以自动启动新进程。出现这种情况时,新请求将由新实例处理,新实例从而变成新的活动进程。但是,指定给旧进程的所有请求仍保持挂起状态。如果旧进程结束了挂起的请求并进入空闲状态,该进程即终止。如果辅助进程崩溃,或者由于其他原因停止处理请求,则所有挂起的请求将被重新指定给新进程。
尽管asp.netisapi和辅助进程是asp.net运行时结构的主要组成部分,但还有其他一些可执行文件也发挥着作用。下表列出了所有这些组件。
表1:构成asp.net运行时环境的可执行文件
名称 类型 帐户 aspnet_isapi.dll win32dll(isapi扩展) localsystem aspnet_wp.exe win32 exeaspnet aspnet_filter.dll win32dll(isapi筛选器) localsystem aspnet_state.exe win32nt serviceaspnet
aspnet_filter.dll组件是一个小的win32isapi筛选器,用来备份asp.net应用程序的无cookie会话状态。在windowsserver2003中,当启用iis6进程模型时,aspnet_filter.dll还将筛选出bin目录中对非可执行资源的请求。
aspnet_state.exe的作用对web应用程序更为重要,因为它用于管理会话状态。该项服务是可选的,可以用来在web应用程序内存空间之外保存会话状态数据。该可执行文件是一种nt服务,既可以在本地运行,也可以远程运行。当该服务被激活后,可以将asp.net应用程序配置为将所有会话信息保存在此进程的内存中。一种类似的方案是提供更为可靠的数据存储方式,不受进程回收和asp.net应用程序故障的影响。该服务在aspnet本地帐户下运行,但可以使用服务控制管理器(servicecontrolmanager)接口来配置它。
另一个应该介绍的可执行文件是aspnet_regiis.exe,尽管严格来讲,它并不属于asp.net运行时结构。该实用程序可以用来配置环境,以在一台计算机上并行执行不同版本的asp.net,还可用于维修iis和asp.net损坏的配置。该实用程序的工作方式是更新存储在iis配置数据库的根目录和子目录中的脚本映射。脚本映射是资源类型和asp.net模块之间的一种关联关系。最后,还可以使用该工具来显示已安装的asp.net版本的状态,执行其他配置操作,如授予对特定文件夹的ntfs权限、创建客户脚本目录。
webgarden模型
webgarden模型可以通过machine.config文件中的<processmodel>部分进行配置。请注意,<processmodel>部分是唯一不能放在应用程序特定的web.config文件中的配置部分。这就是说,webgarden模式可以应用到计算机中运行的所有应用程序。但通过使用machine.config源文件中的<location>节点,可以针对各个应用程序调节计算机的设置。
<processmodel>部分有两个属性可以影响webgarden模型,它们是webgarden和cpumask。webgarden属性接受布尔值,表示是否使用了多个辅助进程(一个相关的cpu对应一个进程)。默认情况下,该属性的值为false。cpumask属性保存一个dword值,该值的二进制表示为能够运行asp.net辅助进程的cpu提供了位屏蔽。其默认值为-1(0xffffff),表示可以使用所有可用的cpu。如果webgarden属性为false,则cpumask属性的内容将被忽略。cpumask属性还为正在运行的aspnet_wp.exe的副本数设置了上限。
常言道“闪光的不都是金子”,用在这里很合适。webgarden模式使得多个辅助进程可以同时运行。但是,需要注意的是所有进程都会有自己的应用程序状态、进程内会话状态、asp.net缓存、静态数据以及运行应用程序所需的其他内容。启用webgarden模式之后,asp.netisapi将根据cpu的数量尽可能多地启动辅助进程,每个辅助进程都是下一进程的完整克隆(每一进程都与相应的cpu密切相关)。为平衡工作负荷,传入的请求以单循环的方式在运行的进程之间进行划分。辅助进程就象在单处理器中一样被回收。请注意,asp.net继承了操作系统中所有的cpu使用限制,并且不包括实现限制的自定义语义。
总之,webgarden模型并不适用于所有应用程序。应用程序的状态越多,其的性能损失也越多。工作数据存储在共享内存的块中,以便一个进程输入的变化可以立即被其他进程得知。但是,处理请求时,工作数据被复制到进程的上下文中。因此,各个辅助进程将处理自己的工作数据,而应用程序的状态越多,性能损失就越大。鉴于此,仔细、明智的应用程序基准测试是绝对必要的。
只有重启iis后,对配置文件中<processmodel>部分所做的更改才会生效。在iis6中,webgarden模式的参数保存在iis配置数据库中,webgarden和cpumask属性被忽略。
http管道
asp.netisapi扩展启动辅助进程后,它将传递部分命令行参数。辅助进程使用这些参数来执行加载clr前需要执行的任务。传递的值包括:com和dcom安全性所要求的身份验证等级、可以使用的命名管道的数量和iis进程标识。命名管道的名称是使用iis进程标识和允许的管道数随机生成的。辅助进程不接收可用管道的名称,但可以接收识别管道名称所需的信息。
com和dcom安全性与microsoft?.netframework有何关系?实际上,clr是作为com对象提供的。更准确地说,clr本身不是由com代码构成的,但是指向clr的接口却是一个com对象。因此,辅助进程加载clr的方式与加载com对象的方式相同。
当aspx请求遇到iis时,web服务器将根据选择的身份验证模型(匿名、windows、basic或digest)来分配一个令牌。当辅助进程收到要处理的请求时,令牌被传递到辅助进程。请求由辅助进程中的线程获取。该线程从最初获取传入请求的iis线程继承身份令牌。在aspnet_wp.exe中,负责处理请求的实际帐户取决于在特殊的asp.net应用程序中是如何配置模拟的。如果模拟被禁用(默认设置),则线程将在辅助进程的帐户下运行。默认情况下,该帐户在asp.net进程模型中为aspnet,在iis6进程模型中为networkservice。这两个帐户都是“弱”帐户,提供的功能比较有限,可以有效抵挡回复性攻击(revert-to-selfattack)。(回复性攻击是指将模拟的客户端的安全性令牌回复到父进程令牌。为辅助进程分配弱帐户可以挫败此类攻击。)
高度概括起来,asp.net辅助进程完成的一项主要任务就是将请求交给一系列称为的http管道的托管对象。要激活http管道,可以创建一个httpruntime类的新实例,然后调用其processrequest方法。如前所述,asp.net中始终只运行一个辅助进程(除非启用了webgarden模型),该进程在独立的appdomain中管理所有的web应用程序。每个appdomain都有自己的httpruntime类实例,即管道中的输入点。httpruntime对象初始化一系列有助于实现请求的内部对象。helper对象包括缓存管理器(cache对象)和内部文件系统监视器(用于检测构成应用程序的源文件的更改)。httpruntime为请求创建上下文,并用与请求相关的http信息填充上下文。上下文用httpcontext类的实例来表示。
另一个在http运行时的设置初期创建的helper对象是文本书写器,用于包含浏览器的响应文本。文本书写器是httpwriter类的实例,此对象对页面代码以编程方式发送的文本进行缓存。http运行时被初始化后,它将查找实现请求的应用程序对象。应用程序对象是httpapplication类的实例,该类就是global.asax文件背后的类。global.asax在编程时是可选的,但在构建结构时是必需的。因此,如果应用程序中没有构建类,则必须使用默认对象。asp.net运行时包括几个中间工厂类,可以用来查找并返回有效的handler对象以处理请求。整个过程中用到的第一个工厂类是httpapplicationfactory。它的主要任务是使用url信息来查找url虚拟目录和汇集的httpapplication对象之间的匹配关系。
应用程序工厂类的行为可以概括为以下几点:
工厂类维护httpapplication对象池,并使用它们来处理应用程序的请求。池的寿命与应用程序的寿命相同。
应用程序的第一个请求到达时,工厂类提取有关应用程序类型的信息(global.asax类)、设置用于监视更改的文件、创建应用程序状态并触发application_onstart事件。
工厂类从池中获取一个httpapplication实例,并将要处理的请求放入实例中。如果没有可用的对象,则创建一个新的httpapplication对象。要创建httpapplication对象,需要先完成global.asax应用程序文件的编译。
httpapplication开始处理请求,并且只能在完成这个请求后才能处理新的请求。如果收到来自同一资源的新请求,则由池中的其他对象来处理。
应用程序对象允许所有注册的http模块对请求进行预处理,并找出最适合处理请求的处理程序类型。这通过查找请求的url的扩展和配置文件中的信息来完成。
http处理程序是一些实现ihttphandler接口的类。.netframework为常见的资源类型提供了一些预定义的处理程序,包括aspx页面和web服务。machine.config文件中的<httphandlers>部分定义了httpapplication对象必须实例化才能处理特定类型资源的请求的类名。如果helper类是一个处理程序工厂,gethandler方法将确定要使用的处理程序类型。这时,将从一组类似的对象中获取适当类型的处理程序,并对其进行配置以处理请求。
ihttphandler接口提供了两个方法:isreusable和processrequest。前者将返回一个布尔值,表示处理程序是否可以被汇集。(大多数预定义的处理程序都是汇集的,但是您可以自行定义每次都需要新实例的处理程序。)processrequest方法包含处理特定类型资源所需的所有逻辑。例如,aspx页面的处理程序基于以下伪代码:
privatevoidprocessrequest()
{
//确定请求是否是回发(postback)
ispostback=determinepostbackmode();
//触发aspx源代码的page_init事件
pageinit();
//加载viewstate,处理已发送的值。
if(ispostback){
loadpageviewstate();
processpostdata();
}
//触发aspx源代码的page_load事件
pageload();
//1)再次处理已发送的值(当
//动态创建控件时)
//2)将属性更改的服务器端事件提升为输入驱动的
//控件(即复选框的状态改变)
//3)执行与回发事件相关的所有代码
processpostdatasecondtry();
raisechangedevents();
raisepostbackevent();
//触发aspx源代码的page_prerender事件
prerender();
//将控件的当前状态保存到viewstate中
savepageviewstate();
//将页面内容呈现给html
rendercontrol(createhtmltextwriter(response.output));
无论调用的资源类型如何,基于http处理程序的模型是相同的。唯一随资源类型变化而变化的元素是处理程序。httpapplication对象负责查找应该使用哪种处理程序来处理请求。httpapplication对象还负责检测对动态创建的、表示资源的程序集(如.aspx页面或.asmxweb服务)所进行的更改。如果检测到更改,应用程序对象将确保编译并加载所请求的资源的最新来源。
临时文件和页面程序集
要全面了解asp.nethttp运行时,让我们来分析一下当请求asp.net页面时,文件系统层所发生的变化。接下来,您将了解由http管道的对象管理和监视的一组动态创建的临时文件。
虽然可以将页面的核心代码隔离在代码背后的c#或microsoft?visualbasic?.net类中,但可以将web页面编写和部署为.aspx文本文件。对于要显示为url的页面来说,.aspx文件在应用程序的web空间中必须始终可用。.aspx文件的实际内容将确定应用程序对象要加载的程序集(或多个程序集)。
按照设计,httpapplication对象将查找一个根据请求的aspx文件命名的类。如果页面命名为sample.aspx,则要加载的相应的类名为asp.sample_aspx。应用程序对象在web应用程序的所有程序集文件夹中查找这样的类,这些文件夹包括全局程序集缓存(gac)、bin子文件夹和temporaryasp.netfiles文件夹。如果未找到这样的类,http结构将分析.aspx文件的源代码,创建一个c#或visualbasic.net类(具体创建哪种类,取决于.aspx页面上设置的语言),同时对其进行编译。新创建的程序集的名称是随机生成的,位于特定于应用程序的子文件夹中,路径如下所示:c:/windows/microsoft.net/framework/v1.1.4322/temporaryasp.netfiles。
子文件夹v1.1.4322特定于asp.net1.1。如果您使用的是asp.net1.0,子文件夹的版本号会有所不同,即子文件夹名为v1.0.3705。再次访问页面时,程序集就已存在,不需要重新创建。但是,httpapplication对象是如何确定特定于页面的程序集是否存在呢?它每次都要扫描大量文件夹吗?不,并不是这样。
应用程序对象只查看temporaryasp.netfiles文件夹中某个特殊文件夹的内容。具体路径(特定于应用程序的路径)由httpruntime.codegendir属性返回。如果是第一次访问.aspx文件(即还未创建页面程序集),则该文件夹中就不存在以aspx页面名称开头的xml文件。例如,具有动态程序集的sample.aspx页面应有如下的条目:
sample.aspx.xxxxx.xml
xxxxx占位符是一种散列代码。通过读取该xml文件的内容,应用程序对象就可以了解要加载的程序集的名称以及要在其中获取的类。以下代码片段是这种helper文件的典型内容。包含asp.sample_aspx类的程序集的名称是mvxvx8xr。
<preserveassem="mvxvx8xr"type="asp.sample_aspx">
<filedepname="c:/inetpub/wwwroot/vdir/sample.aspx"/>
</preserve>
当然,只有在分析filedep文件的源代码以生成动态程序集时才创建该文件。对filedep文件所做的任何更改都会使程序集无效,在下一次请求时必须重新编译。需要注意的是,在asp.net架构的未来版本中,该实现过程可能会有较大改变。不论什么原因,只要您决定在当前应用程序中使用它,都必须十分小心。
由于更新而要为页面创建新的程序集时,asp.net将验证是否可以删除旧的程序集。如果旧的程序集只包含修改后的页面的类,asp.net将试图删除并替换该程序集,否则将在保留旧程序集的情况下创建一个新程序集。
在删除过程中,asp.net可能会发现程序集文件已被加载并锁定。这种情况下,可以为旧程序集添加一个“.delete”扩展名,以将其重新命名。(注意,所有windows文件都可以在使用过程中重新命名。)只要应用程序重新启动(例如,由于对某个应用程序文件如global.asax和web.config进行了更改),这些临时的.delete文件就将被删除。但在处理下一个请求时,asp.net运行时不会删除这些文件。
请注意,默认情况下,在整个应用程序重新启动之前,每个asp.net应用程序最多可以重新编译15个页面,同时会损失一些会话和应用程序数据。当最近的编译次数超过了<httpruntime>部分的numrecompilesbeforeapprestart属性中设置的阈值时,将卸载appdomain,并重新启动应用程序。还要注意,在.netframework中,您无法卸载单个程序集。appdomain是可以从clr卸载的最小的代码块。
小结
asp.net应用程序有两大特征:进程模型和页面对象模型。asp.net提前使用了iis6.0的一些功能,而iis6.0则是windowsserver2003中提供的全新的、开创性的microsoftweb信息服务。尤其值得一提的是,在独立的辅助进程中运行的asp.net应用程序,其行为与iis6中的所有应用程序相同。而且,尽管会出现运行时异常、内存泄露或程序错误,asp.net运行时仍能自动回收辅助进程以保证实现卓越的性能。这种功能已成为iis6.0的系统功能。
在本文中,我概括介绍了默认的asp.net进程模型的基础知识,以及iis级代码(asp.netisapi扩展)和辅助进程之间的交互。同时,还介绍了与iis6进程模型之间的最新区别。