产品简介
您看到过出色的咖啡店店员送咖啡的情景吗?那简直就是咖啡豆、蒸汽和牛奶调和咖啡饮料在跳精彩的芭蕾,跳跃着奔向焦急等候的顾客。然而,即便是最好的店员偶尔也会出现问题。比如两个单子在处理时搞混了,结果送到您面前的是一杯 Soy latte。也可能是杯子上龙飞凤舞的潦草字迹根本就是写错了,或者店员理解错了。有人要了一杯“卞高奇若”(卡普其诺),可怜的店员绞尽脑汁也弄不懂顾客到底要点什么。如果出现了类似的问题,就必须停止处理,然后再重新开始。好的服务员可能会推延一下现有的要求,而优秀的服务员却能够在没人察觉的情况下做到这一点。
Microsoft® ASP.NET 在系统可靠性方面取得了优于其任何竞争对手的巨大进步。然而,就像那位出色的店员一样,ASP.NET 偶尔也会出现问题。幸运的是,ASP.NET 是非常优秀的服务器。它能在后台迅速生成新的进程,然后处理请求。通常只会在请求页面时发生一点用户甚至可能都不会注意到的轻微延迟。
而 ASP.NET 系统的管理员可能需要知道发生了什么。同样,他们也想了解是什么原因导致了进程失败。幸运的是,使用 .NET Framework 类库文档中的 ProcessInfo 和 ProcessModelInfo 类便可获得相关信息。本文中,我们将学习如何创建 ASP.NET HTTP 处理程序,以使用这些对象查看 Web 站点使用的进程的运行状况和关闭状况。另外,我们将创建一个配置节处理程序,这样我们便能够在安装处理程序后对其进行配置。
我们将看到什么?
ASP.NET 进程负责编译和管理所有向 ASP.NET 页面提出的请求。理想状况下,此进程应该始终存在于服务器中:活跃地接收请求、编译页面并返回 HTML。然而,由于存在许多可能影响进程的潜在事件,我们不得不面对 Web 开发的真实状况。开发人员可能未能正确处理内存泄漏或线程问题;服务器可能会丢失与进程的连接;或者甚至会因为在 Web.config 文件的 <processModel> Element 节中对 idleTimeout、requestLimit、memoryLimit 和类似的项目进行了错误的配置而导致出现问题。如果发生了以上任何一种事件,则将创建新的 ASP.NET 辅助进程,新的请求将移交至此进程进行处理。
由于 ASP.NET 进程对页面处理如此重要,因此监视这些进程同样重要。使用 ProcessInfo 和 ProcessModelInfo 类可以查看当前和以前进程的有效期和运行状况。图 1 所示为在本文中创建的进程列表。
图 1:Web 服务器的进程历史记录
ProcessInfo class 存储了给定进程的数据。不得自行创建 ProcessInfo 类,但可以使用 ProcessModelInfo class 来检索 ProcessInfo 对象。表 1 所示为 ProcessInfo 类的重要属性。
表 1:ProcessInfo 类的属性
属性数据类型说明AgeTimeSpan进程运行(或曾经运行)的总时间。如果这个值超出了在 Web.Config 文件的 processModel 节中的超时设置,可导致重新启动进程。PeakMemoryUsedInteger此进程所用内存的最大值(以 MB 为单位)。如果这个值超出了在 Web.Config 文件的 processModel 节设置的 memoryLimit 级别设置,可导致进程重新启动。ProcessIDInteger操作系统使用此 ID 来标识进程。每个进程均有唯一的 ID(在进程运行时)。RequestCountInteger进程接收到的页面请求的数量。如果这个值超出了在 Web.Config 文件的 processModel 中 requestLimit 的级别设置,可导致进程重新启动。ShutdownReasonProcessShutdownReason此枚举定义进程重新启动的可能原因。有关可能的值,请参阅表 2。StartTimeDateTime进程启动的时间。StatusProcessStatus此枚举定义 ASP.NET 辅助进程的当前状态。此值可能为 Alive(活动)、ShuttingDown(进程已接收到关闭请求)、ShutDown(进程已正常关闭)或 Terminated(进程已被迫关闭)。某进程关闭后,关闭原因将被设置为 ProcessShutdownReason Enumeration 中的某一个值。
表 2:进程关闭的可能原因
值说明None此值表明进程仍在运行。Timeout进程因其生存期超出了在 Web.Config 文件的 processModel 节中设置的超时值而重启。如果这种情况频繁发生,也许应考虑增加超时值。不过,一般来说因这种原因而重新启动可以接受。IdleTimeout进程重新启动的原因是缺少客户端。如果在 Web.Config 文件的 processModel 节中的 idleTimeout 值所设置的时间期限内没有客户端请求,将发生此类重新启动。这通常也是可以接受的重新启动的原因。RequestsLimit进程重新启动的原因是接收到的请求数量超过了在 Web.Config 文件的 processModel 节中设置的值 (requestLimit)。一般来说,这种重新启动的原因也可以接受,主要用于您希望进程偶尔重新启动的情况。MemoryLimitExceeded进程重新启动的原因是因为超出了通过 Web.Config 文件中的 memoryLimit 值设置的内存限制。这通常表示进程的某个 ASP.NET 应用程序部分发生了问题(可能是内存泄漏)。如果此类现象频繁发生,请监视每个 Web 应用程序的内存使用是否正常。RequestQueueLimit进程重新启动的原因是在等候响应的请求总数超出了 Web.Config 文件的 requestQueueLimit 值。通常这是某些情况将导致 Web 服务器延迟的信号。可能需要增加内存或服务器,或提高驱动器或处理器的速度。DeadlockSuspected进程重新启动的原因是可能停止了正在处理的请求。正如这个关闭原因的名称那样,最可能导致这种情况的原因为:如果两个或多个线程需要另一个线程完成后才能继续进行(例如 A 线程需要 B 线程完成向某文件的写入后才能继续进行,而同时 B 线程需要 A 线程完成计算后才能继续进行),我们将这种情况称为线程处于“Deadlock”(死锁)状态。如果有这种可能,进程将因此而关闭。一般来说,您肯定不希望看到这种关闭原因,如果您不幸看到了,请查看在应用程序中使用的所有线程处理或资源使用情况。PingFailed当 ASP.NET 辅助进程管理页面时,有时会收到从 IIS 进程发来的 ping 以确定是否仍需要此进程。如果 ping 失败,则 IIS 进程可能会关闭该 ASP.NET 进程。这个关闭原因说明了可能在服务器接收消息的过程中确实存在通信问题或 ASP.NET 辅助进程因某种原因而停止工作。Unexpected一般来说,您肯定不想看到此消息,因为它表明是“某种其他原因”终止了 ASP.NET 辅助进程。除了监视每个进程或对所有运行中的代码执行代码校对,几乎没有任何办法解决此问题。创建进程查看处理程序
在 ASP.NET 中,主要使用两种方法来创建 HTTP 处理程序。第一种是通过创建带有 ASHX 扩展名的文件,另一种是创建实现 System.Web.IHttpHandler 的类,请参阅 IHttpHandler Interface。本文将着重介绍第二种形式。要创建 HTTP 处理程序,需要创建一个程序集(通常是一个代码库项目)和一个实现 System.Web.IHttpHandler 的类。然后将该类注册到 Web.Config(或 machine.config)文件中,然后它就可以接收请求了。如果查看 machine.config 文件(在相应命名的 httpHandlers 节中),将看到许多当前已注册的 HTTP 处理程序,包括 System.Web.UI.PageHandlerFactory(ASP.NET 页面的主处理程序)。在编写 HTTP 处理程序时,其实就是在定义处理请求的新方法。
所有 HTTP 处理程序均通过实现 System.Web.IHttpHandler Interface 来创建。此接口需要创建一个属性和一个方法,如表 3 所示。
表 3:IHttpHandler 接口的成员
成员类型说明IsReusable属性 (Boolean)确定该处理程序的实例是否可以重复使用。通常,该属性应返回 true,除非处理程序需要对某个资源的独占访问。ProcessRequest方法HTTP 处理程序的“主”方法。将在此添加对请求的所有处理。该类传递当前 ASP.NET 上下文。可以从此上下文中检索请求对象和响应对象。实现 IHttpHandler
创建 HTTP 处理程序的大量工作集中在实现处理程序的 ProcessRequest。通常,需要存储当前上下文的请求和响应对象,然后使用响应对象的编写方法创建输出。以下给出了用于进程查看处理程序的 ProcessRequest 资源的 Microsoft Visual Basic® .NET 源。
Public Sub ProcessRequest(ByVal context As HttpContext) _ Implements IHttpHandler.ProcessRequest _context = context _writer = New HtmlTextWriter(context.Response.Output) 'we only want to do this if we're enabled If _config.Enabled Then _writer.WriteLine("<html>") _writer.WriteLine("<head>") _writer.WriteLine(Me.StyleSheet) _writer.WriteLine("</head>") _writer.WriteLine("<body>") _writer.WriteLine("<span class=""content"">") 'write content here 'create table Dim t As New Table() With t .Width = Unit.Percentage(100) .CellPadding = 0 .CellSpacing = 0 End With 'the meat of the routine 'make certain this is a destination machine If (PermittedHost(_context.Request.UserHostAddress)) Then CreateHeader(t) AddProcesses(t) CreateFooter(t) Else CreateErrorReport(t) End If 'write to the stream t.RenderControl(_writer) _writer.WriteLine("</span> </body> </html>") End If End Sub
ProcessRequest 的实现会存储当前上下文和编写者。然后,它通过呈现页面的起始 HTML 标签将新的 HTML 页面创建为输出。下一步,它将创建一个用于格式化输出的表格。最后,如果启用了处理程序,并且发出请求的客户端是合法的 IP 地址之一,则通过以下三种方法创建输出: CreateHeader、AddProcesses 和 CreateFooter。这些方法将相应的值呈现在表格的单元格中。这些代码有很大一部分是重复的,为了简短起见,以下仅给出了 AddProcesses 及其相关的方法。
Private Sub AddProcesses(ByVal table As _ System.Web.UI.WebControls.Table) Dim procs As ProcessInfo() = _ ProcessModelInfo.GetHistory(_config.RequestLimit) Dim row As TableRow _list = New ProcessInfoCollection For Each proc As ProcessInfo In procs row = AddRow(table) _list.Add(proc) AddCell(row, proc.ProcessID.ToString()) AddCell(row, proc.Status.ToString()) AddCell(row, proc.StartTime.ToString("g")) AddCell(row, FormatAge(proc.Age)) AddCell(row, proc.PeakMemoryUsed.ToString("N0") + " MB") AddCell(row, proc.RequestCount.ToString("N0")) AddCell(row, proc.ShutdownReason.ToString()) Next End Sub Private Function AddCell( _ ByVal row As System.Web.UI.WebControls.TableRow, _ ByVal text As String) As System.Web.UI.WebControls.TableCell Dim c As New TableCell() c.Text = text row.Cells.Add© Return c End Function
细心的(和有技术背景的)读者可能已经注意到,我完全可以通过呈现 DataGrid 并将 ProcessInfoCollection 绑定到 DataGrid 来简化此代码,但那样就失去了编写程序的乐趣。
安装 HTTP 处理程序
创建完 HTTP 处理程序后,必须进行安装才能使用。这包括使类可用,并在配置文件中添加相应的信息以激活处理程序。
如果创建的是仅被单个 vroot 使用的简单处理程序,则可以将 DLL 复制至该 vroot 的 bin 目录即可使用该类。如果创建了一个由多个 vroot 使用的 HTTP 处理程序(类似于 ProcessHandler),则此处理程序必须安装到全局程序集缓存 (GAC) 中。要将此处理程序安装到 GAC 中,类必须具有严格名称。要具有严格名称,它必须有关联的严格名称键。必须使用命令行可执行文件 sn.exe 创建严格名称键文件。有关此程序的详细信息,请参阅 NET Framework Tools 文档的 Strong Name Tool (Sn.exe) 一节。
处理程序可用后,下一步就是添加配置以使其可以处理请求,方法是在 Web.Config 或 machine.config 文件的 httpHandlers 节中添加条目。此条目指定了将通过处理程序路由的文件扩展名和操作。进程查看处理程序的条目如下所示。
<add verb="*" path="process.axd" type="Microsoft.Samples.Msdn.Web.ProcessHandler, MsdnProcessHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f5f94c20bb90ce64" />
此条目意味着在某个请求使用任何 HTTP 命令寻找“文件” process.axd(实际上不存在)时,它将向位于程序集 MsdnProcessHandler 中的 Microsoft.Samples.Msdn.Web.ProcessHandler 类发送请求。该类将实现 IHttpHandler,然后由 IHttpHandler 负责生成输出。
添加配置
许多 ASP.NET 应用程序使用 appSetting 标签添加自定义配置。这对于大多数应用程序来说已经完全足够了。然而,有时应用程序可以使用更有针对性的解决方案。这种情况下,您可以为应用程序新建节。
新建配置节包括两个步骤。首先,必须创建配置对象。此对象或结构具有表示所需配置数据的属性。此对象可以具有、但通常不具有任何方法。其次要创建一个节处理程序。此节处理程序负责从 web.congfig 文件中读取相应的信息,并且将其转化为配置对象。
ProcessViewer 的配置对象具有四个属性,如下表所述。
表 4:ProcessViewer 配置对象的属性
属性数据类型说明EnabledBoolean如果 ProcessViewer 可用,则为 true。这样便可以暂时关闭处理程序而无需将其从 web.config 文件中删除。LocalOnlyBoolean如果只能从本地计算机查看 ProcessViewer 的输出,则为 true。这是最为安全的方案,防止其他人查看 Web 应用程序的进程历史记录。RequestLimitInteger该属性限定了显示项目数的最大值。ProcessModelInfo.GetHistory 最多返回 100 个项目。此属性用于在需要时减少此数量。PermittedHostsString array如果 LocalOnly 为 false,则任何计算机均可以访问 Process.axd handler 来查看应用程序的进程历史记录。这就可能会有安全风险。因此,可以分配允许访问处理程序的 IP 地址的列表。此属性可用于限制对管理员工作站的访问。创建自定义配置的第二步是创建解释配置文件的 XML 的类,并使用该信息填充配置对象。此类必须实现 System.Configuration.IConfigurationSectionHandler 接口。此接口只有一个方法,称为 Create。以下给出了 ProcessViewerSectionHandler 的 Visual Basic .NET 源(请参阅 C# 源的下载)。
Public Function Create(ByVal parent As Object, _ ByVal configContext As Object, _ ByVal section As System.Xml.XmlNode) As Object _ Implements Configuration.IConfigurationSectionHandler.Create ' 节具有以下格式: '<processView ' localOnly="true|false" ' requestLimit="<=100" ' enabled="true|false" ' permittedHosts="comma-delimited list of IP addresses" /> Dim result New ProcessViewerConfiguration() Dim config As New ConfigurationHelper(section) Dim max As Integer Dim hosts As String Const delimiter As String = ", " Const MaximumReturnCount As Integer = 100 '确认设置,并设定 result.Enabled = config.GetBooleanAttribute("enabled") result.LocalOnly = config.GetBooleanAttribute("localOnly") max = config.GetIntegerAttribute("requestLimit") If max <= MaximumR