代码改变世界

【基本原理】使用托管代码实现一个寄宿ASP.NET的HTTP服务器(上)

2010-07-31 12:22  GUO Xingwang  阅读(2168)  评论(17编辑  收藏  举报

  在这两篇文章中我将带着大家实现一个简单的ASP.NET的寄宿环境,本文是第一部分,主要讲述一些原理性的内容,其中大多使用自己的通俗的语言描述一些概念和基础性的内容,为了给读者带来更多的认识,本文也解释了一些最原始的概念,有些内容是自己的总结,如有不对或理解不一致的地方请读者指出。唉,本人实在不善于写文章,写了只是为了总结一些。

  1.ASP.NET的Pipeline模型 

  ASP.NET随着.NET Framework一起分发,从1.0开始就一直以IIS作为寄宿环境。ASP.NET更是一个HTTP的处理引擎,内部实现了一套Pipeline模型,实现流水线处理请求的设计,从构造上线文环境(HttpContext)开始,经过一系列的过滤器(HttpModule,至于如何安装网上已经有很多介绍,HttpModule类似win32下的钩子Hook,可以做一些过滤处理,很实用的),请求会被送达处理器(HttpHandler,这个处理器可以完成对这个请求的最终处理),最后再经过一些过滤器而通过寄宿环境返回给客户端。需要说明的是这个Pipeline是一些讲述ASP.NET Runtime的文章中经常提到的概念,学习win32的同学们千万不要把这个Pipeline理解成Windows下的匿名或命名管道,它们完全是两个不同的概念,最初学习时很容易混淆,虽然在IIS内处理请求时与ASP.NET的WORKER进程之间通信等很多地方采用了命名管道,但是他们也不是一回事。至于讲述ASP.NET中Pipeline详细的文章网上到处都是,这里只是提及而已。

  2.IIS的寄宿ASP.NET的基本工作原理

  这里提及一下IIS的进程模型,IIS的进程模型从IIS5到IIS6是一个很大的飞跃,主要是IIS6把http协议栈实现在了内核模式中,那就是http.sys协议型驱动程序。这对于Windows作为Http服务器的性能是一个很大的提升,http.sys完成了连接的管理和请求路由(根据配置路由给相应的工作进程处理,这个配置可根据IIS的配置动态调整),http.sys与tcp.sys类似都是内核级的协议驱动程序,http.sys构建在tcpip.sys之上,就像http使用tcp协议承载一样,但是tcpip.sys实现的不仅是tcp/ip协议栈,它实现了很多协议的处理。在IIS5中使用socket处理http协议,但是socket是在用户态下运行的,所以处理http请求时会有大量的CPU从用户态和核心态之间的切换过程,这种切换的代价还是很大的,这对于大并发的http请求是不适合的。socket(这里的socket确切的说应该是 win sock)依赖于tcpip.sys完成标准接口功能,而tcpip.sys运行在内核态。从Windows xp sp2和Windows server 2003以后的操作系统中,微软提供了http.sys,http.sys在处理请求时,只有对于有必要投递给WORKER进程的请求,http.sys才会把请求数据根据动态的路由表路由给相应的进程处理。而且http.sys还完成了以下工作:如果当前要路由的进程没有启动,那么它会缓存住这个请求(放入请求队列中),直到有工作进程处理这个请求它才会投递。毫不夸张的说IIS在http.sys出现以后才成为真正的Http服务器。

   这里插一句:之前有人曾经问过java与.net相互调用有几种方式,之后有人会说使用webservice,socket等。从上文的分析来看这种说法可能不太合适,说socket不如说tcp或udp等准确。因为从上文我们就可以看出socket只不过是一种标准的编程接口,除此之外并不代表什么,它的作用只是使编程风格统一化,使用统一的模型使用tcp等协议,实际上在调用时完全可以一端使用socket,而另一端使用ndis模拟协议实现,但是估计没有人会这么干,因为已经有现成的东西可用了。
也就是说使用底层的驱动程序还有很多机制,例如本文说到的http.sys这也是使用底层协议的很好的例子。以后我可能会详细介绍ndis驱动模型,使用ndis可以模拟各种协议发送数据包。
  还要插一句:说一说我们常说的网络端口是什么?例如http常用的80端口,那么80到底是什么啊,看过tcp协议详解的同学会马上注意到端口的这种概念是出现在传输层的,IP层只涉及到IP地址为止,而传输层协议,例如tcp,udp等都会加入一个端口号,这其实是标准协议里规定的,实际上端口号在操作系统中只是一种标识,协议驱动程序会根据端口号和更详细的信息进行路由,例如我们说某个应用程序监听了80端口,那么驱动程序就会把协议中具有80端口的请求路由给这个监听程序。说白了就是一种标识,实现时也没有复杂的数据结构。当然这些概念都已经标准化了。

  2.托管的HttpListener类型

  HttpListener是一个.net的类型,可以使用它在.net平台下实现一个http的服务器,这个类型有很多成员,这个同学们可以参考msdn的相关文档,这也不是本文的重点。HttpListener实际上是基于httpapi.dll的,那么httpapi.dll是做什么的呢?我理解这个dll就是win32下对于http.sys核心态驱动程序功能的一个用户态的封装而已,使用httpapi.dll的导出函数,用户程序可以从用户态切换到到内核态使用http.sys,这就像kernel32.dll中的很多函数一样,实现了内核态系统服务的调用。那么有了这个用户带的dll就好办了。而HttpListener正是这个dll在.net平台下的封装,这样.net也可以间接的使用http.sys了。这也就为构建.net下的http服务器成为可能,因为这些基础性的东西微软已经为我们做好了。无论从理论还是实际上来看IIS6和HttpListener处理请求的性能是相当的。

  3.如何寄宿ASP.NET运行时环境

观察IIS寄宿Asp.net的原理,我们会发现,真正的托管代码是从构建HttpWorkerRequest对象开始的,可以理解为IIS搜集了http请求的大量信息,之后构造了托管的HttpWorkerRequest对象,这个对象也就是托管和非托管的过度的关键,而后HttpWorkerRequest再去生成HttpContext对象,而这个HttpContext就是贯穿于Asp.net的pipeline模型始终的上下文对象。在.net进程中如何寄宿Asp.net的运行时呢?我们可以使用微软专门为寄宿Asp.net运行时而提供的的ApplicationHost类型(位于System.Web.Hosting)。System.Web.Hosting这个名称空间中有很多类型来处理其他种进程寄宿Asp.net运行时。代码类似:

代码
internal AspxNetEngine(String virtualAlias, String physicalDir)
        {
            m_VirtualAlias 
= virtualAlias;
            m_PhysicalDirectory 
= physicalDir;

            Console.WriteLine(
"Creating a new AspxEngine.");

            
//m_ExecutingEngine will be the actual object that the hosting API created for 
            
//us and so to execute a page in the Application we will call this object to 
            
//process requests
            m_ExecutingAppDomain = (AspxNetEngine)ApplicationHost.CreateApplicationHost(typeof(AspxNetEngine), m_VirtualAlias,m_PhysicalDirectory);

            Console.WriteLine(
"New AspxEngine created for alias " + m_VirtualAlias);
        }

ApplicationHost.CreateApplicationHost方法会创建一个新的应用程序域,并AspxNetEngine作为应用程序域通信的对象,所以AspxNetEngine必须是MarshalByRefObject。

  基于以上资料我们就完全可以使用托管代码实现一个简单寄宿Asp.net的运行时的环境,这里的说的“简单”主要是比起IIS还有一些性能问题和其他方面的问题,下文将详细说明遇到的相关问题。其实这种实现网上有很多种,例如classin或vs自带的开发服务器,这种东西应用到实际的生产环境还是有问题的,IIS我们轻易不能放弃。