Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

[翻译] ASP.NET内幕 - ISAPI和应用程序域之间的桥梁

原文地址:ASP.NET Internals - The bridge between ISAPI and Application Domains

2007.06.13
Simone Busoli

ASP.NET内部工作原理有时候看起来像发生在幕后的黑暗魔法,为开发者提供了一个较高层次的抽象,使得他们只用关注应用程序的业务逻辑,而不需要处理HTTP协议底层机制。ASP.NET基于.NET框架开发,应用于.NET框架,因此绝大部分是托管代码。然而web请求的入口点是使用本地代码(native code)编写的web服务器,因此需要一个通讯机制。这篇文章中我将描述托管和非托管世界的桥梁,它们怎样协作设置ASP.NET处理请求时所需的处理环境。

参考第一部分:ASP.NET内幕 - IIS处理模型

介绍

在前一篇,也是这一系列的第一篇中,我介绍了web服务器接收到web请求之后进行的第一步处理,以及如果被确定为ASP.NET资源请求时如何路由请求。你已经明白不同的IIS版本在处理ASP.NET相关请求时的差异,最终请求被分发到一个叫做aspnet_isapi.dll的非托管Win32组件,这个组件的作用就是web服务器和托管ASP.NET架构之间的桥梁。

这篇文章中我将继续讨论上一篇中的遗留问题,开始研究处理环境的托管部分,在前一篇文章中它被称作ASP.NET运行时环境,作为一个黑盒子提到过。

注意:ASP.NET运行时环境有时也叫做ASP.NET管道、Http运行时管道,或者是这些单词的混合。

从aspnet_isapi.dll扩展到托管世界

前一篇文章中我解释了IIS 5和IIS 6怎样管理ASP.NET请求,忽略它们怎样处理工作进程的创建、管理和回收,最后所有与请求相关的讯息归结到了aspnet_isapi.dll扩展上。

这是非托管和托管世界的桥梁,也是所有ASP.NET架构中最缺乏文档的部分。

注意:在写这篇文章的时候,IIS 7以一个上线许可与Longhorn Server Beta 3一起发布了。IIS 7中许多事情会改变,尽管这些前期文章只是关注之前的IIS版本,以后的文章将会致力于IIS 7中引入的改进。


因为在这个主题的很多方面缺乏可用的文档,我将进行的解释可能不完全正确,特别是涉及到非托管结构的部分。然而我关于未文档化部分主题的考量,是基于一些对理解框架内部工作原理很有帮助的工具进行的:
回到非托管和托管世界的桥梁上来,不管是IIS 6处理模式的情况下通过ISAPI扩展,还是IIS 5处理模式的情况下通过工作进程,在CLR加载之后,黑暗魔法出现了。ASP.NET ISAPI组件通过位于System.Web.Hosting命名空间中的两个托管类,调用一大把的非托管COM接口,通过COM接口方式暴露这些方法的类是AppManagerAppDomainFactory和ISAPIRuntime。

注意:公共语言运行时CLR掌管每一个.NET应用程序的执行环境,它为运行托管应用程序提供环境和服务。CLR必须运行在Win32进程中,ASP.NET是.NET框架为CLR提供的一个宿主之一,确切地说在ASP.NET中,ASP.NET工作进程(IIS 5中为aspnet_wp.exe,IIS 6中为w3wp.exe)就是运行CLR的进程。

在研究这些类中发生的交互行为的技术细节之前,我们先大致看一下处理请求时究竟发生了什么。我前面已经介绍过这两个类,因为处理请求的入口点可以大致的分为两个方面。

    1. 如果AppDomain还不存在则创建APPDomain,将AppDomain指派给与请求对应的应用程序,这通过AppManagerAppDomain类实现。
    2. 提交和处理请求,由ISAPIRuntime类实现。

这两个方面都很重要,第一个包括了不需要开发者参与的一些交互动作,主要涉及应用程序的运行环境,而第二个则是结构上最可配置的部分,我将在这个系列的后续文章中完整地探讨。

从另一个角度来看,第一步包含的操作在应用程序生存期里只执行一次,就是说在启动的时候,而第二步包含的交互在每一次定位请求的目标应用程序时都会出现。

创建AppDomain

在前面文章中了解到,一个ASP.NET应用程序被封装成一个叫做应用程序域的实体,简写AppDomain,由ASP.NET架构中的一个类AppDomain表示。当特定应用程序的请求到达时,如果不存在则必须创建AppDomain对象,这通常发生在属于特定应用程序的请求第一次到达时,或者由于某种原因相应的AppDomain被关闭掉了,几个可能导致这个问题的原因我在后面会谈到。注意一个ASP.NET应用程序只会有一个AppDomain存在,它跟IIS应用程序一对一映射,可能基于物理目录或者虚拟目录创建。那么这个类的实例是怎样创建的呢?

图1:创建AppDomain实例时由JetBrains dotTrace Profiler产生的调用栈


使用Reflector可以推断AppDomain有一个抛出NotSupportedException异常的私有构造器,因此很明显这个方法行不通。实际上初始化AppDomain实例的入口点是AppManagerAppDomainFactory.Create方法,为了找出这一点,需要使用一个分析器跟踪初始化对象时生成的调用栈。图1显示了JetBrains dotTrac Profiler的截屏,它显示了运行在IIS上的一个web应用程序在初始化AppDomain时生成的调用栈。有很多类参与了这个过程,它们也许是开发者永远不需要用到的。

注意:AppManagerAppDomainFactory类在CLR初始化过程中只运行一次,就像它的名字暗示的,它作为创建AppDomain以及其他重要对象的入口点。

图2显示了一个跟前面图1一样的调用栈,这次是从微软CLR Profiler调用树的输出中截取的,它为AppDomain的初始化提供了更多的信息。实际上高亮行表明主线程创建了两个AppDomain类的实例。

图2:微软CLR Profiler调用树视图显示的创建AppDomain实例时的调用栈


那么是怎样创建这个额外的实例呢,为什么创建?很不幸这不是个容易的问题。前面图中显示的调用栈,指示了在创建与实际执行应用程序相关的AppDomain实例时采用的步骤,因此这个额外的实例,看起来像是用于某些难于理解的目的的辅助对象,尽我所能的猜测,它被用于容纳所有不属于任何应用程序的对象,例如AppManagerAppDomainFactory,因此被放置在一个隔离的AppDomain中。对于AppManagerAppDomainFactory类,这个辅助AppDomain只在CLR初始化时实例化一次,它在CLR初始化很早的时候进行,如图3所示。

图3:额外AppDomain实例的顺序和分配方法


图3展示了Red Gate ANTS Profiler的一个截屏,它显示了额外AppDomain实例在CLR初始化很早的时候创建,很明显它比AppManagerAppDomainFactory类更早创建,因为很有可能它是AppManagerAppDomainFactory的容器。实际上在前面分析会话图示中,单例AppManagerAppDomainFactory实例的ID为52。

另一个查看前面提到的两个AppDomain实例初始化过程的方法,是微软CLR Profiler提供的分配视图,它创建一个图形流程,展示处理中类的使用位置,像图4显示那样。

图4:微软CLR Profiler分配视图显示的两个AppDomain初始化过程


它展示了一个平面视图,从根元素,即CLR,和线程类之间裁剪出所有的类,然而它清晰的显示了创建两个AppDomain时的调用顺序。

从前面图中可以得到另外一个关于AppDomain实例的信息是,每个实例占用100字节内存。这不会带来多少问题,但有人会认为AppDomain是一个很大的类,因为它必须容纳整个应用程序。实际上它提供很多的服务,但并不存储太多数据。

到现在辅助AppDomain实例已经被创建了,包括AppManagerAppDomainFactory类,它的Create方法就是图1和2中显示的调用栈的入口方法。

这个方法通过COM暴露出来,提供给调用者,因此是非托管代码。AppManagerAppDomainFactory类实现了 IAppManagerAppDomainFactory接口,它的结构显示在图5中。

图5:IAppManagerAppDomainFactory接口结构


这个接口使用ComImport和InterfaceType属性修饰,它们将类型绑定到非托管接口类型上。调用这个方法时,将产生图1和2的调用栈图形,最终触发AppDomain类实例的创建,容纳用于处理请求的目标应用程序。

AppDomain对象创建以及运行之后,剩余工作就是处理请求,这比前面已经完成的工作内容更多,这也许是ASP.NET架构中最有意思的部分,但在这个有趣的部分开始之前我已经介绍了一些关键的主题。

总结

这篇文章中我介绍了ASP.NET基础结构中一些很底层的主题,涉及那些由ASP.NET ISAPI扩展展示的非托管世界,以及运行web应用程序的AppDomain所展示的托管世界之间的一些接口。我展示了初始化AppDomain的机制,以及哪些类参与了这个处理。下一篇文章中我会通过HTTP管道讨论托管代码,在我的映像里这应当是ASP.NET架构最吸引人的一章,实际上它使得ASP.NET不同于其他所有的web开发框架。我希望你像我写这篇文章时一样充满兴趣的阅读,我的建议是打开一些分析工具自己尝试这些内容,这是充分理解它怎样工作的最好方法。

引用

posted on 2007-07-09 20:30  riccc  阅读(2664)  评论(5编辑  收藏  举报

导航