您看到過出色的咖啡店店員送咖啡的情景嗎?那簡直就是咖啡豆、蒸汽和牛奶調(diào)和咖啡飲料在跳精彩的芭蕾,跳躍著奔向焦急等候的顧客。然而,即便是最好的店員偶爾也會出現(xiàn)問題。比如兩個單子在處理時搞混了,結(jié)果送到您面前的是一杯 Soy latte。也可能是杯子上龍飛鳳舞的潦草字跡根本就是寫錯了,或者店員理解錯了。有人要了一杯“卞高奇若”(卡普其諾),可憐的店員絞盡腦汁也弄不懂顧客到底要點什么。如果出現(xiàn)了類似的問題,就必須停止處理,然后再重新開始。好的服務(wù)員可能會推延一下現(xiàn)有的要求,而優(yōu)秀的服務(wù)員卻能夠在沒人察覺的情況下做到這一點。
Microsoft® asp.net 在系統(tǒng)可靠性方面取得了優(yōu)于其任何競爭對手的巨大進(jìn)步。然而,就像那位出色的店員一樣,ASP.NET 偶爾也會出現(xiàn)問題。幸運(yùn)的是,ASP.NET 是非常優(yōu)秀的服務(wù)器。它能在后臺迅速生成新的進(jìn)程,然后處理請求。通常只會在請求頁面時發(fā)生一點用戶甚至可能都不會注意到的輕微延遲。
而 ASP.NET 系統(tǒng)的管理員可能需要知道發(fā)生了什么。同樣,他們也想了解是什么原因?qū)е铝诉M(jìn)程失敗。幸運(yùn)的是,使用 .NET Framework 類庫文檔中的 PRocessInfo 和 ProcessModelInfo 類便可獲得相關(guān)信息。本文中,我們將學(xué)習(xí)如何創(chuàng)建 ASP.NET HTTP 處理程序,以使用這些對象查看 Web 站點使用的進(jìn)程的運(yùn)行狀況和關(guān)閉狀況。另外,我們將創(chuàng)建一個配置節(jié)處理程序,這樣我們便能夠在安裝處理程序后對其進(jìn)行配置。
ASP.NET 進(jìn)程負(fù)責(zé)編譯和管理所有向 ASP.NET 頁面提出的請求。理想狀況下,此進(jìn)程應(yīng)該始終存在于服務(wù)器中:活躍地接收請求、編譯頁面并返回 HTML。然而,由于存在許多可能影響進(jìn)程的潛在事件,我們不得不面對 Web 開發(fā)的真實狀況。開發(fā)人員可能未能正確處理內(nèi)存泄漏或線程問題;服務(wù)器可能會丟失與進(jìn)程的連接;或者甚至?xí)驗樵?Web.config 文件的 <processModel> Element 節(jié)中對 idleTimeout、requestLimit、memoryLimit 和類似的項目進(jìn)行了錯誤的配置而導(dǎo)致出現(xiàn)問題。如果發(fā)生了以上任何一種事件,則將創(chuàng)建新的 ASP.NET 輔助進(jìn)程,新的請求將移交至此進(jìn)程進(jìn)行處理。
由于 ASP.NET 進(jìn)程對頁面處理如此重要,因此監(jiān)視這些進(jìn)程同樣重要。使用 ProcessInfo 和 ProcessModelInfo 類可以查看當(dāng)前和以前進(jìn)程的有效期和運(yùn)行狀況。圖 1 所示為在本文中創(chuàng)建的進(jìn)程列表。

圖 1:Web 服務(wù)器的進(jìn)程歷史記錄
ProcessInfo class 存儲了給定進(jìn)程的數(shù)據(jù)。不得自行創(chuàng)建 ProcessInfo 類,但可以使用 ProcessModelInfo class 來檢索 ProcessInfo 對象。表 1 所示為 ProcessInfo 類的重要屬性。
表 1:ProcessInfo 類的屬性
| 屬性 | 數(shù)據(jù)類型 | 說明 |
| Age | TimeSpan | 進(jìn)程運(yùn)行(或曾經(jīng)運(yùn)行)的總時間。如果這個值超出了在 Web.Config 文件的 processModel 節(jié)中的超時設(shè)置,可導(dǎo)致重新啟動進(jìn)程。 |
| PeakMemoryUsed | Integer | 此進(jìn)程所用內(nèi)存的最大值(以 MB 為單位)。如果這個值超出了在 Web.Config 文件的 processModel 節(jié)設(shè)置的 memoryLimit 級別設(shè)置,可導(dǎo)致進(jìn)程重新啟動。 |
| ProcessID | Integer | 操作系統(tǒng)使用此 ID 來標(biāo)識進(jìn)程。每個進(jìn)程均有唯一的 ID(在進(jìn)程運(yùn)行時)。 |
| RequestCount | Integer | 進(jìn)程接收到的頁面請求的數(shù)量。如果這個值超出了在 Web.Config 文件的 processModel 中 requestLimit 的級別設(shè)置,可導(dǎo)致進(jìn)程重新啟動。 |
| ShutdownReason | ProcessShutdownReason | 此枚舉定義進(jìn)程重新啟動的可能原因。有關(guān)可能的值,請參閱表 2。 |
| StartTime | DateTime | 進(jìn)程啟動的時間。 |
| Status | ProcessStatus | 此枚舉定義 ASP.NET 輔助進(jìn)程的當(dāng)前狀態(tài)。此值可能為 Alive(活動)、ShuttingDown(進(jìn)程已接收到關(guān)閉請求)、ShutDown(進(jìn)程已正常關(guān)閉)或 Terminated(進(jìn)程已被迫關(guān)閉)。 |
某進(jìn)程關(guān)閉后,關(guān)閉原因?qū)⒈辉O(shè)置為 ProcessShutdownReason Enumeration 中的某一個值。
表 2:進(jìn)程關(guān)閉的可能原因
| 值 | 說明 |
| None | 此值表明進(jìn)程仍在運(yùn)行。 |
| Timeout | 進(jìn)程因其生存期超出了在 Web.Config 文件的 processModel 節(jié)中設(shè)置的超時值而重啟。如果這種情況頻繁發(fā)生,也許應(yīng)考慮增加超時值。不過,一般來說因這種原因而重新啟動可以接受。 |
| IdleTimeout | 進(jìn)程重新啟動的原因是缺少客戶端。如果在 Web.Config 文件的 processModel 節(jié)中的 idleTimeout 值所設(shè)置的時間期限內(nèi)沒有客戶端請求,將發(fā)生此類重新啟動。這通常也是可以接受的重新啟動的原因。 |
| RequestsLimit | 進(jìn)程重新啟動的原因是接收到的請求數(shù)量超過了在 Web.Config 文件的 processModel 節(jié)中設(shè)置的值 (requestLimit)。一般來說,這種重新啟動的原因也可以接受,主要用于您希望進(jìn)程偶爾重新啟動的情況。 |
| MemoryLimitExceeded | 進(jìn)程重新啟動的原因是因為超出了通過 Web.Config 文件中的 memoryLimit 值設(shè)置的內(nèi)存限制。這通常表示進(jìn)程的某個 ASP.NET 應(yīng)用程序部分發(fā)生了問題(可能是內(nèi)存泄漏)。如果此類現(xiàn)象頻繁發(fā)生,請監(jiān)視每個 Web 應(yīng)用程序的內(nèi)存使用是否正常。 |
| RequestQueueLimit | 進(jìn)程重新啟動的原因是在等候響應(yīng)的請求總數(shù)超出了 Web.Config 文件的 requestQueueLimit 值。通常這是某些情況將導(dǎo)致 Web 服務(wù)器延遲的信號??赡苄枰黾觾?nèi)存或服務(wù)器,或提高驅(qū)動器或處理器的速度。 |
| DeadlockSuspected | 進(jìn)程重新啟動的原因是可能停止了正在處理的請求。正如這個關(guān)閉原因的名稱那樣,最可能導(dǎo)致這種情況的原因為:如果兩個或多個線程需要另一個線程完成后才能繼續(xù)進(jìn)行(例如 A 線程需要 B 線程完成向某文件的寫入后才能繼續(xù)進(jìn)行,而同時 B 線程需要 A 線程完成計算后才能繼續(xù)進(jìn)行),我們將這種情況稱為線程處于“Deadlock”(死鎖)狀態(tài)。如果有這種可能,進(jìn)程將因此而關(guān)閉。一般來說,您肯定不希望看到這種關(guān)閉原因,如果您不幸看到了,請查看在應(yīng)用程序中使用的所有線程處理或資源使用情況。 |
| PingFailed | 當(dāng) ASP.NET 輔助進(jìn)程管理頁面時,有時會收到從 IIS 進(jìn)程發(fā)來的 ping 以確定是否仍需要此進(jìn)程。如果 ping 失敗,則 IIS 進(jìn)程可能會關(guān)閉該 ASP.NET 進(jìn)程。這個關(guān)閉原因說明了可能在服務(wù)器接收消息的過程中確實存在通信問題或 ASP.NET 輔助進(jìn)程因某種原因而停止工作。 |
| Unexpected | 一般來說,您肯定不想看到此消息,因為它表明是“某種其他原因”終止了 ASP.NET 輔助進(jìn)程。除了監(jiān)視每個進(jìn)程或?qū)λ羞\(yùn)行中的代碼執(zhí)行代碼校對,幾乎沒有任何辦法解決此問題。 |
在 ASP.NET 中,主要使用兩種方法來創(chuàng)建 HTTP 處理程序。第一種是通過創(chuàng)建帶有 ASHX 擴(kuò)展名的文件,另一種是創(chuàng)建實現(xiàn) System.Web.IHttpHandler 的類,請參閱 IHttpHandler Interface。本文將著重介紹第二種形式。要創(chuàng)建 HTTP 處理程序,需要創(chuàng)建一個程序集(通常是一個代碼庫項目)和一個實現(xiàn) System.Web.IHttpHandler 的類。然后將該類注冊到 Web.Config(或 machine.config)文件中,然后它就可以接收請求了。如果查看 machine.config 文件(在相應(yīng)命名的 httpHandlers 節(jié)中),將看到許多當(dāng)前已注冊的 HTTP 處理程序,包括 System.Web.UI.PageHandlerFactory(ASP.NET 頁面的主處理程序)。在編寫 HTTP 處理程序時,其實就是在定義處理請求的新方法。
所有 HTTP 處理程序均通過實現(xiàn) System.Web.IHttpHandler Interface 來創(chuàng)建。此接口需要創(chuàng)建一個屬性和一個方法,如表 3 所示。
表 3:IHttpHandler 接口的成員
| 成員 | 類型 | 說明 |
| IsReusable | 屬性 (Boolean) | 確定該處理程序的實例是否可以重復(fù)使用。通常,該屬性應(yīng)返回 true,除非處理程序需要對某個資源的獨占訪問。 |
| ProcessRequest | 方法 | HTTP 處理程序的“主”方法。將在此添加對請求的所有處理。該類傳遞當(dāng)前 ASP.NET 上下文??梢詮拇松舷挛闹袡z索請求對象和響應(yīng)對象。 |
創(chuàng)建 HTTP 處理程序的大量工作集中在實現(xiàn)處理程序的 ProcessRequest。通常,需要存儲當(dāng)前上下文的請求和響應(yīng)對象,然后使用響應(yīng)對象的編寫方法創(chuàng)建輸出。以下給出了用于進(jìn)程查看處理程序的 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>/r/n</body>/r/n</html>") End If End SubProcessRequest 的實現(xiàn)會存儲當(dāng)前上下文和編寫者。然后,它通過呈現(xiàn)頁面的起始 HTML 標(biāo)簽將新的 HTML 頁面創(chuàng)建為輸出。下一步,它將創(chuàng)建一個用于格式化輸出的表格。最后,如果啟用了處理程序,并且發(fā)出請求的客戶端是合法的 ip 地址之一,則通過以下三種方法創(chuàng)建輸出: CreateHeader、AddProcesses 和 CreateFooter。這些方法將相應(yīng)的值呈現(xiàn)在表格的單元格中。這些代碼有很大一部分是重復(fù)的,為了簡短起見,以下僅給出了 AddProcesses 及其相關(guān)的方法。
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細(xì)心的(和有技術(shù)背景的)讀者可能已經(jīng)注意到,我完全可以通過呈現(xiàn) DataGrid 并將 ProcessInfoCollection 綁定到 DataGrid 來簡化此代碼,但那樣就失去了編寫程序的樂趣。
創(chuàng)建完 HTTP 處理程序后,必須進(jìn)行安裝才能使用。這包括使類可用,并在配置文件中添加相應(yīng)的信息以激活處理程序。
如果創(chuàng)建的是僅被單個 vroot 使用的簡單處理程序,則可以將 DLL 復(fù)制至該 vroot 的 bin 目錄即可使用該類。如果創(chuàng)建了一個由多個 vroot 使用的 HTTP 處理程序(類似于 ProcessHandler),則此處理程序必須安裝到全局程序集緩存 (GAC) 中。要將此處理程序安裝到 GAC 中,類必須具有嚴(yán)格名稱。要具有嚴(yán)格名稱,它必須有關(guān)聯(lián)的嚴(yán)格名稱鍵。必須使用命令行可執(zhí)行文件 sn.exe 創(chuàng)建嚴(yán)格名稱鍵文件。有關(guān)此程序的詳細(xì)信息,請參閱 NET Framework Tools 文檔的 Strong Name Tool (Sn.exe) 一節(jié)。
處理程序可用后,下一步就是添加配置以使其可以處理請求,方法是在 Web.Config 或 machine.config 文件的 httpHandlers 節(jié)中添加條目。此條目指定了將通過處理程序路由的文件擴(kuò)展名和操作。進(jìn)程查看處理程序的條目如下所示。
<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 類發(fā)送請求。該類將實現(xiàn) IHttpHandler,然后由 IHttpHandler 負(fù)責(zé)生成輸出。
許多 ASP.NET 應(yīng)用程序使用 appSetting 標(biāo)簽添加自定義配置。這對于大多數(shù)應(yīng)用程序來說已經(jīng)完全足夠了。然而,有時應(yīng)用程序可以使用更有針對性的解決方案。這種情況下,您可以為應(yīng)用程序新建節(jié)。
新建配置節(jié)包括兩個步驟。首先,必須創(chuàng)建配置對象。此對象或結(jié)構(gòu)具有表示所需配置數(shù)據(jù)的屬性。此對象可以具有、但通常不具有任何方法。其次要創(chuàng)建一個節(jié)處理程序。此節(jié)處理程序負(fù)責(zé)從 web.congfig 文件中讀取相應(yīng)的信息,并且將其轉(zhuǎn)化為配置對象。
ProcessViewer 的配置對象具有四個屬性,如下表所述。
表 4:ProcessViewer 配置對象的屬性
| 屬性 | 數(shù)據(jù)類型 | 說明 |
| Enabled | Boolean | 如果 ProcessViewer 可用,則為 true。這樣便可以暫時關(guān)閉處理程序而無需將其從 web.config 文件中刪除。 |
| LocalOnly | Boolean | 如果只能從本地計算機(jī)查看 ProcessViewer 的輸出,則為 true。這是最為安全的方案,防止其他人查看 Web 應(yīng)用程序的進(jìn)程歷史記錄。 |
| RequestLimit | Integer | 該屬性限定了顯示項目數(shù)的最大值。ProcessModelInfo.GetHistory 最多返回 100 個項目。此屬性用于在需要時減少此數(shù)量。 |
| PermittedHosts | String array | 如果 LocalOnly 為 false,則任何計算機(jī)均可以訪問 Process.axd handler 來查看應(yīng)用程序的進(jìn)程歷史記錄。這就可能會有安全風(fēng)險。因此,可以分配允許訪問處理程序的 IP 地址的列表。此屬性可用于限制對管理員工作站的訪問。 |
創(chuàng)建自定義配置的第二步是創(chuàng)建解釋配置文件的 xml 的類,并使用該信息填充配置對象。此類必須實現(xiàn) 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 ' 節(jié)具有以下格式: '<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 '確認(rèn)設(shè)置,并設(shè)定 result.Enabled = config.GetBooleanAttribute("enabled") result.LocalOnly = config.GetBooleanAttribute("localOnly") max = config.GetIntegerAttribute("requestLimit") If max <= MaximumReturnCount Then result.requestLimit = max End If hosts = config.GetStringAttribute("permittedHosts") result.PermittedHosts = hosts.Split(delimiter.ToCharArray()) Return result End FunctionCreate 方法傳遞了三個對象:
以上代碼使用了 ConfigurationHelper 對象。此對象是一個用于從節(jié)中檢索特定數(shù)據(jù)類型的簡單對象。此類的代碼如下所示。
Friend Class ConfigurationHelper Dim _section As XmlNode Public Sub New(ByVal configSection As XmlNode) _section = configSection End Sub '接受 true/false、yes/no Public Function GetBooleanAttribute(ByVal name As String) As Boolean Dim value As String Dim result As Boolean value = GetStringAttribute(name).ToLower() If ((Boolean.TrueString.ToLower() = value) _ OrElse (value = "yes")) Then result = True Else result = False End If Return result End Function Public Function GetIntegerAttribute(ByVal name As String) As Integer Dim value As String Dim result As Integer value = GetStringAttribute(name) result = Int32.Parse(value) Return result End Function Public Function GetStringAttribute(ByVal name As String) As String Dim theAttribute As XmlAttribute Dim result As String theAttribute = _section.Attributes(name) If Not theAttribute Is Nothing Then result = theAttribute.Value End If Return result End FunctionEnd Class
要使用這個節(jié)處理程序和配置對象,必須將其注冊在相應(yīng)的 ASP.NET 配置文件中。因為可以在任何進(jìn)程中調(diào)用該類,所以最好將其添加到 machine.config 文件中。在 machine.config 類的 configSection 節(jié)中的相應(yīng)位置注冊節(jié)處理程序(我將其添加到了 system.web 節(jié)中)
<sectionGroup name="system.web"> ... other sections <section name="processView" type="Microsoft.Samples.Msdn.Web.ProcessViewerSectionHandler, MsdnProcessHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f5f94c20bb90ce64" /> </sectionGroup>
注冊完畢后,便可以向 machine.config 文件中添加節(jié),并且該類將成為可配置類。
創(chuàng)建 HTTP 處理程序可以提供超出 ASP.NET. 功能的強(qiáng)有力機(jī)制,使得開發(fā)者避開頁面模型,并創(chuàng)建、修改或擴(kuò)展 Web 站點的內(nèi)容。通過添加用于查看 ASP.NET 的進(jìn)程歷史記錄的 HTTP 處理程序,可以診斷代碼或服務(wù)器中導(dǎo)致客戶投訴的問題,例如代碼中存在內(nèi)存泄漏或未處理的異常,或服務(wù)器的內(nèi)存不足。
創(chuàng)建并安裝 HTTP 處理程序后,您將可以更敏銳地發(fā)現(xiàn)在此重要進(jìn)程中發(fā)生的狀況。到時候您就會有時間來喝杯咖啡,而不是忙于追查 Web 站點進(jìn)程重新啟動的原因了。
新聞熱點
疑難解答