Web Server and ASP.NET Application Life Cycle in Depth






4.87/5 (119投票s)
本文深入介绍了 Web 应用程序中请求/响应之间发生的所有事情。
引言
在本文中,我们将尝试理解用户向 ASP.NET Web 应用程序提交请求时会发生什么。有很多文章解释了这个主题,但没有一篇以清晰的方式展示在请求过程中真正发生的深入情况。阅读本文后,您将能够理解
- 什么是 Web 服务器
- HTTP - TCP/IP 协议
- IIS
- Web 通信
- 应用程序管理器
- 托管环境
- 应用程序域
- 应用程序池
- 针对客户端请求创建了多少个应用程序域
- 针对请求创建了多少个 HttpApplication,以及我如何影响这种行为
- 什么是工作进程,以及有多少个工作进程针对请求运行
- 请求和响应之间发生了什么
从零开始
我读过的所有文章通常都以“用户向 IIS 发送请求……等等”开头。每个人都知道 IIS 是一个托管我们 Web 应用程序(以及更多)的 Web 服务器,但什么是 Web 服务器?
让我们从真正的开始说起。
Web 服务器(如 Internet Information Server/Apache/等)是一种软件,它允许使用 HTTP 查看网站。我们可以抽象地将这个概念理解为,Web 服务器是一种允许通过 HTTP 协议请求资源的软件(网页、图像等)。我相信很多人会认为 Web 服务器只是一个特殊的超级计算机,但实际上是运行在上面的软件才使得普通计算机和 Web 服务器有所区别。
众所周知,在 Web 通信中,主要有两个参与者:客户端和服务器。
客户端和服务器当然需要一个连接才能相互通信,并需要一套通用的规则才能互相理解。他们沟通所需的规则称为协议。从概念上讲,当我们与某人交谈时,我们就在使用一种协议。人类沟通中的协议是关于外表、说话、倾听和理解的规则。这些规则,也称为对话协议,代表了不同层次的沟通。它们共同作用,帮助人们成功沟通。计算系统也需要协议。通信协议是数字消息格式的正式描述以及在计算系统之间或在电信中交换这些消息的规则。
HTTP 知道所有的“语法”,但它不知道如何发送消息或打开连接。这就是为什么 HTTP 构建在 TCP/IP 之上。下面,您可以看到这个协议在 HTTP 协议之上的概念模型。
TCP/IP 负责管理连接以及在客户端和服务器之间交换消息所需的所有低级操作。
在本文中,我将不解释 TCP/IP 的工作原理,因为这需要写一整篇文章,但了解它是使客户端和服务器能够进行消息交换的引擎很重要。
HTTP 是一个无连接协议,但这并不意味着客户端和服务器在开始相互通信之前不需要建立连接。但是,它意味着客户端和服务器在开始通信之前不需要进行任何预先安排。
无连接意味着客户端不关心服务器是否准备好接受请求,另一方面,服务器也不关心客户端是否准备好接收响应,但仍然需要连接。
在面向连接的通信中,通信对等方必须在交换用户数据之前的对话中先建立逻辑或物理数据通道或连接。
现在,让我们看看当用户在浏览器地址栏输入地址时会发生什么。
- 浏览器将 URL 分为三部分
- 协议(“HTTP”)
- 服务器名称(www.Pelusoft.co.uk)
- 文件名(index.html)
- 浏览器与名称服务器通信,将服务器名称“www.Pelusoft.co.uk”解析为 IP 地址,然后使用该 IP 地址连接到服务器计算机。
- 然后,浏览器在端口 80 上与该 IP 地址的服务器建立连接。(我们将在本文后面讨论端口。)
- 根据 HTTP 协议,浏览器向服务器发送 GET 请求,请求文件“http://www.pelusoft.co.uk.com/index.htm”。(请注意,cookie 可能会从浏览器发送到服务器与 GET 请求一起发送 - 有关详细信息,请参阅Cookie 的工作原理。)
- 然后,服务器将网页的 HTML 文本发送到浏览器。(cookie 也可能在页面标头中从服务器发送到浏览器。)
- 浏览器读取 HTML 标签并在屏幕上格式化页面。
目前的做法要求客户端在每次请求之前建立连接,并在服务器发送响应后关闭连接。客户端和服务器都应意识到,任何一方都可能因用户操作、自动超时或程序故障而过早关闭连接,并应以可预测的方式处理这种关闭。在任何情况下,双方或一方关闭连接始终会终止当前请求,无论其状态如何。
至此,您应该对 HTTP - TCP/IP 协议的工作原理有所了解。当然,还有很多内容可以讨论,但本文的范围只是对这些协议进行非常高层次的概述,以便更好地理解用户开始浏览网站以来发生的所有步骤。
现在是时候继续了,将重点转移到 Web 服务器收到请求时会发生什么,以及它如何获取该请求。
正如我之前展示的,Web 服务器是一个“普通计算机”,它运行着使其成为 Web 服务器的特殊软件。假设我们的 Web 服务器上运行着 IIS。从非常高的角度来看,IIS 只是一个在特定端口(通常是 80)上侦听的进程。侦听意味着它已准备好接受来自端口 80 的客户端连接。要记住的一个非常重要的事情是:IIS 不是 ASP.NET。这意味着 IIS 对 ASP.NET 一无所知;它可以独立工作。我们可以有一个 Web 服务器,它只托管 HTML 页面或图像或任何其他类型的 Web 资源。正如我之前解释的,Web 服务器只需要返回浏览器所请求的资源。
ASP.NET 和 IIS
Web 服务器还可以支持服务器脚本(如 ASP.NET)。我在本节中介绍的内容是 ASP.NET 服务器上发生的情况,以及 IIS 如何与 ASP.NET 引擎“通信”。当我们向服务器安装 ASP.NET 时,安装会将应用程序的脚本映射更新到相应的 ISAPI 扩展,以处理 IIS 收到的请求。例如,“aspx”扩展将被映射到 aspnet_isapi.dll,因此 IIS 对 aspx 页面的请求将被交给 aspnet_isapi(也可以使用 Aspnet_regiis 进行 ASP.NET 注册)。脚本映射如下所示:
ISAPI 过滤器是一个插件,可以在 IIS 看到 HTTP 数据流之前访问它。如果没有 ISAPI 过滤器,IIS 就无法将请求重定向到 ASP.NET 引擎(在 .aspx 页面情况下)。从非常高的角度来看,我们可以将 ISAPI 过滤器视为 IIS 请求的路由器:每次请求的文件的扩展名存在于映射表中(上面显示的那个),它就会将请求重定向到正确的位置。对于 .aspx 页面,它会将请求重定向到 .NET 运行时,该运行时知道如何处理该请求。现在,让我们看看它是如何工作的。
当请求到来时
- 如果工作进程 (w3wp.exe) 未运行,IIS 将创建/运行它。
- aspnet_isapi.dll 托管在 w3wp.exe 进程中。IIS 检查脚本映射并将请求路由到 aspnet_isapi.dll。
- 请求被传递给也托管在 w3wp.exe 中的 .NET 运行时。
最后,请求进入运行时
本节重点介绍运行时如何处理请求,并展示了过程中涉及的所有对象。
首先,让我们看看请求到达运行时时会发生什么。
- 当 ASP.NET 收到应用程序中任何资源的第一个请求时,一个名为
ApplicationManager
的类会创建一个应用程序域。(应用程序域提供应用程序之间的全局变量隔离,并允许每个应用程序独立卸载。) - 在应用程序域内,会创建一个名为 Hosting Environment 的类实例,该实例提供对应用程序信息的访问,例如应用程序存储的文件夹名称。
- 在创建应用程序域并实例化 Hosting Environment 对象后,ASP.NET 会创建并初始化核心对象,例如
HttpContext
、HttpRequest
和HttpResponse
。 - 在所有核心应用程序对象初始化后,应用程序通过创建
HttpApplication
类的实例来启动。 - 如果应用程序有一个 Global.asax 文件,ASP.NET 会创建一个派生自
HttpApplication
类的 Global.asax 类实例,并使用该派生类来表示应用程序。
这些是针对客户端请求发生的第一个步骤。大多数文章都不会提及这些步骤。在本文中,我们将深入分析每个步骤发生的情况。
下面,您可以看到请求在被处理之前必须经过的所有步骤。
应用程序管理器
我们需要讨论的第一个对象是应用程序管理器。
应用程序管理器实际上是一个位于所有正在运行的 ASP.NET AppDomains 之上的对象,它可以执行诸如关闭所有应用程序域或检查空闲状态等操作。
例如,当您更改 Web 应用程序的配置文件时,应用程序管理器负责重新启动应用程序域,以便所有正在运行的应用程序实例(您的网站实例)可以重新创建以加载您可能已更改的新配置文件。
正在管道处理中的请求将继续通过现有管道运行,而任何新进来的请求将被路由到新的应用程序域。为避免“挂起请求”问题,ASP.NET 会在请求超时后强制关闭应用程序域,即使仍有挂起请求。
应用程序管理器是“管理器”,但托管环境(Hosting Environment)包含管理应用程序实例的“逻辑”。就像一个类使用一个接口一样:在类的方法中,您只需调用接口方法。在这种情况下,方法是在应用程序管理器中调用的,但在托管环境中执行(假设托管环境是实现接口的类)。
此时,您应该有一个疑问:应用程序管理器如何与托管环境通信,因为它存在于应用程序域中?(我们说过应用程序域为应用程序创建了一种边界,以隔离应用程序本身。)事实上,托管环境必须继承自 MarshalByRefObject
类才能使用 Remoting 与应用程序管理器通信。应用程序管理器创建一个远程对象(托管环境)并对其调用方法 :-)
因此,我们可以说托管环境是应用程序管理器使用的“远程接口”,但代码是在托管环境对象“内”执行的。
HttpApplication
在上一节中,我使用了大量的“应用程序”一词。HttpApplication
是您 Web 应用程序的一个实例。它是负责“处理”请求并返回要发送回客户端的响应的对象。HttpApplication
一次只能处理一个请求。但是,为了最大化性能,HttpApplication
实例可以被重用以处理多个请求,但它一次只执行一个请求。
这简化了应用程序事件处理,因为您不需要在访问应用程序类的非静态成员时锁定它们。这还允许您在应用程序类的非静态成员中存储请求特定的数据。例如,您可以在 Global.asax 文件中定义一个属性并为其分配一个请求特定的值。
您不能手动创建 HttpApplication
的实例;应用程序管理器负责执行此操作。您只能配置您希望应用程序管理器创建的 HttpApplication
的最大数量。机器配置文件中有许多键可以影响应用程序管理器的行为。
<processModel enable="true|false"
timeout="hrs:mins:secs|Infinite"
idleTimeout="hrs:mins:secs|Infinite"
shutdownTimeout="hrs:mins:secs|Infinite"
requestLimit="num|Infinite"
requestQueueLimit="num|Infinite"
restartQueueLimit="num|Infinite"
memoryLimit="percent"
webGarden="true|false"
cpuMask="num"
userName="<username>"
password="<secure password>"
logLevel="All|None|Errors"
clientConnectedCheck="hrs:mins:secs|Infinite"
comAuthenticationLevel="Default|None|Connect|Call|
Pkt|PktIntegrity|PktPrivacy"
comImpersonationLevel="Default|Anonymous|Identify|
Impersonate|Delegate"
responseDeadlockInterval="hrs:mins:secs|Infinite"
responseRestartDeadlockInterval="hrs:mins:secs|Infinite"
autoConfig="true|false"
maxWorkerThreads="num"
maxIoThreads="num"
minWorkerThreads="num"
minIoThreads="num"
serverErrorMessageFile=""
pingFrequency="Infinite"
pingTimeout="Infinite"
maxAppDomains="2000"
/>
使用 maxWorkerThreads
和 minWorkerThreads
,您可以设置 HttpApplication
的最小和最大数量。
有关更多信息,请参阅:ProcessModel Element。
为了澄清我们到目前为止所说的内容,我们可以说,对于 Web 应用程序的请求,我们有:
- 启动一个工作进程 w3wp.exe(如果它未运行)。
- 创建一个
ApplicationManager
实例。 - 创建一个应用程序池。
- 创建一个托管环境实例。
- 创建一个
HttpAplication
实例池(由 machine.config 定义)。
到目前为止,我们只讨论了一个 Web 应用程序,假设是 IIS 下的 WebSite1。如果我们为 WebSite2 在 IIS 下创建另一个应用程序会怎样?
- 我们将经历上面解释的相同过程。
- WebSite2 将在现有的工作进程 w3wp.exe(WebSite1 正在运行的地方)中执行。
- 同一个应用程序管理器实例也将管理 WebSite2。每个工作进程 w3wp.exe 始终有一个实例。
- WebSite2 将拥有自己的应用程序域和托管环境。
- 将在新的应用程序域内运行 WebSite2 的实例(
HttpApplication
实例)。
非常重要的是要注意,每个 Web 应用程序都在一个单独的应用程序域中运行,这样如果一个应用程序失败或出现问题,就不会影响其他 Web 应用程序,它们可以继续工作。此时,我们应该有另一个问题:
如果一个 Web 应用程序(例如 WebSite1)出现问题影响了工作进程(即使这相当困难),会发生什么?
如果我想回收应用程序域呢?
总结一下我们所说的,应用程序池由一个或多个进程组成。您运行的每个 Web 应用程序都由(通常是,我没记错的话)一个应用程序域组成。问题在于,当您将多个 Web 应用程序分配到同一个应用程序池时,虽然它们由应用程序域边界分隔,但它们仍在同一个进程(w3wp.exe)中。这可能不如为每个 Web 应用程序使用单独的应用程序池可靠/安全。另一方面,通过减少多个进程的开销,它可以提高性能。
Internet Information Services (IIS) 应用程序池是 URL 的分组,这些 URL 被路由到一个或多个工作进程。因为应用程序池定义了一组共享一个或多个工作进程的 Web 应用程序,所以它们提供了一种方便的方式来管理一组 Web 站点和应用程序及其相应的工作进程。进程边界分隔每个工作进程;因此,应用程序池中的 Web 站点或应用程序不会受到其他应用程序池中应用程序问题的影响。应用程序池极大地提高了 Web 基础架构的可靠性和可管理性。
我想更好地理解应用程序域和应用程序池之间的区别
为了更好地理解,假设您正在使用 Internet Explorer。如您所知,您可以在同一个 IE 实例中打开多个选项卡来浏览多个网站。
假设您打开了四个选项卡来浏览不同的网站,这样每个选项卡都会显示一个不同的网站。
喝了很多杯啤酒后,让我们假设每个选项卡都在不同的应用程序域中运行,并且我们浏览的每个网站都是 IIS 下的不同 Web 应用程序。我们浏览的所有网站都共享同一个 IE 实例(iexplorer.exe)。在这个例子中,让我们将 IE 实例(iexplorer.exe)与 ASP.NET 工作进程(w3wp.exe)进行比较。如果一个选项卡因任何原因卡住(正如有时会发生的那样 :-)),它将影响在同一个 iexplorer.exe 实例下运行的所有其他选项卡(有时 IE 会卡住,并且不响应任何交互,显示无响应),因此您被迫终止进程才能继续工作。
通过终止进程,您将无法浏览您正在浏览的任何网站,即使只有一个进程导致 IE 卡住。
那么,如果我们想在进程级别隔离网站浏览,以便如果一个网站出现问题,我们可以继续浏览其他网站,该怎么办?很简单,您可以打开两个 IE 实例,并在每个实例中打开两个选项卡。在这种情况下,将运行两个 iexplore.exe 实例,它们将完全独立:如果一个 Internet Explorer 实例中的选项卡卡住了,您可以终止该进程,但另一个进程将继续工作。
至此,您应该对 Web 请求过程中发生的深入情况有所了解。
当然,关于这个主题还有很多话要说,但本文的范围只是让您了解 IIS/ASP.NET 内部真正发生的事情以及为满足 Web 请求而创建的所有对象。