簡介
不管使用哪種底層平台,可靠性和性能都是對所有web應用程式的主要要求,盡管從某種意義上講,這兩個要求是互相沖突的。例如,要建構更可靠、更健壯的應用程式,可能需要将web伺服器與具體的應用程式分離,使應用程式在程序外工作。但是,如果在不同于web伺服器程序的記憶體環境中工作,應用程式将變慢。是以,需要采取合理的措施,以確定程序外代碼盡可能快地運作。
在建構microsoft?asp.net運作時環境時,依據的設計原則即:充分考慮可靠性和性能。得到的asp.net程序模型包含了兩個系統元素-一個存在于web伺服器程序中的程序内連接配接器,一個外部的輔助程序。另外,asp.net運作時結構的可伸縮能力很強,可以自動使用多處理器硬體中任意標明的處理器。這種模式被稱為“webgarden”,它可以使多個輔助程序同時運作,而且各個程序均在獨立的處理器中。
高度概括起來,asp.net運作時具有三大屬性:
應用程式和asp.net輔助程序之間完全分離。提供服務的輔助程序的壽命決不會影響應用程式的壽命。換句話說,當應用程式啟動并處于運作狀态時,輔助程序可以随時終止。
盡管asp.net應用程式從不在web伺服器内采用程序内的方式運作,但大多數情況下,其總體性能仍接近于程序内應用程式的性能。
為webgarden體系結構提供了内置的和可配置的支援。隻要簡單檢查一下配置檔案中的設定,輔助程序就可以克隆自己,以利用所有與程序密切相關的cpu。是以,在大多數情況下,您在具備多處理器的計算機中獲得的可縮放性将呈線性增長的趨勢。(本文後面将詳細介紹此内容。)
本文将介紹asp.net運作時環境的組成元素,然後一步一步地講述從url請求變為純html文本的“漫長而曲折”的過程。
除非另有說明,否則以下介紹中均指asp.net的預設程序模型,即microsoft?internetinformationservices(iis)5.x中唯一的模型。
asp.net結構的元件
執行asp.net應用程式需要宿主web伺服器的支援。在microsoft?windows?的server平台中,web伺服器由名為inetinfo.exe的iis可執行檔案表示。windows2000及以上版本的作業系統本身均提供了web伺服器。但需要注意,在microsoft?windowsserver™2003中,并未預設安裝iis和asp.net,必須通過單擊“控制台”中的“添加或删除程式”小程式将其添加到系統中。
iis是一個未托管的可執行程式,它提供了一個基于isapi擴充子產品和篩選器子產品的可擴充模型。通過編寫此類子產品,開發人員可以直接管理對特定資源類型的請求,并在各個預定義的步驟中接收目前請求。擴充和篩選器是一些dll,可以導出一些具有已知名稱和簽名的函數。這些插件元件是在iis配置資料庫中注冊并配置的。
隻有少數幾種被用戶端請求的資源類型由iis直接處理。例如,對html頁面、文本檔案、jpeg和gif圖像的傳入請求由iis處理。對activeserverpage(*.asp)檔案的請求通過調用名為asp.dll的asp專用擴充子產品進行解析。同樣,對asp.net資源(例如,*.aspx、*.asmx、*.ashx)的請求将傳遞到asp.netisapi擴充。該系統元件是一個名為aspnet_isapi.dll的win32dll。asp.net擴充可以處理多種資源類型,包括web服務和http處理程式調用。
asp.netisapi擴充是一個win32dll,未內建托管代碼。它是接收和分派對各種asp.net資源的請求的控制中心。按照設計,該子產品存在于iis程序中,在具有管理者權限的system帳戶下運作。開發人員和系統管理者不能修改此帳戶。asp.netisapi擴充負責調用asp.net輔助程序(aspnet_wp.exe),而該程序又負責控制請求的執行。除了對請求進行安排以外,asp.netisapi還監視輔助程序的運作情況,并在性能降低到一定程度時将程序取消。
輔助程序是一小段win32shell代碼,內建了公共語言運作庫(clr)并運作托管代碼。它負責處理對aspx、asmx和ashx資源的請求。一般來說,此程序在一台給定的計算機中隻有一個執行個體。所有目前激活的asp.net應用程式均在其中運作,每個應用程式都位于一個獨立的appdomain中。但是,如前所述,輔助程序支援webgarden模式,即程序的相同副本都運作在與程序密切相關的cpu中。(更多内容,請參閱本文後面的“webgarden模型”部分。)
isapi和輔助程序之間的通訊是使用一組命名管道進行的。命名管道是一種win32機制,用于跨程序邊界傳輸資料。顧名思義,命名管道的工作方式與管道相似:在一端輸入資料,在另一端輸出相同的資料。建立的管道既可以連接配接本地程序,也可以連接配接遠端計算機上運作的程序。對于本地程序間通訊,管道是windows中的最有效、最靈活的工具。
為確定獲得最優性能,aspnet_isapi使用異步命名管道來将請求轉發給輔助程序并獲得響應。另一方面,輔助程序在需要查詢有關iis環境的資訊(即伺服器變量)時又使用同步管道。aspnet_isapi子產品建立固定數量的命名管道,并使用重疊的操作以通過小的線程池處理同一時間進行的連接配接。當通過管道進行的資料交換操作結束後,完成例程将斷開用戶端,并重新使用管道執行個體為新的用戶端服務。線程池和重疊操作均可以保證使asp.netisapi的性能達到令人滿意的水準。但是,aspnet_isapi擴充決不會處理http請求。
asp.net請求的處理邏輯可以概括為以下步驟:
當請求到達時,iis檢查資源類型并調用asp.netisapi擴充。如果啟用了預設的程序模型,aspnet_isapi會将請求排隊,并将請求配置設定給輔助程序。所有的請求資料都通過異步i/o發送。如果啟用了iis6程序模型,請求将自動在輔助程序(w3wp.exe)中排隊,此輔助程序用于處理應用程式所屬的iis應用程式池。iis6輔助程序不了解asp.net和托管代碼的任何情況,它隻是處理*.aspx擴充并加載aspnet_isapi子產品。當asp.netisapi在iis6程序模型中運作時,它的工作方式有所不同,僅在w3wp.exe輔助程序的上下文中加載clr。
收到請求後,asp.net輔助程序将通知asp.netisapi,它将為請求服務。通知通過同步i/o實作。之是以使用同步模型,是因為請求隻有在isapi内部請求表中被标記為“executing”,輔助程序才能開始處理它。如果請求已經由特殊的輔助程序進行處理,則不能再将它指定到其他程序,除非原始程序已取消。
在輔助程序的上下文中執行請求。有時,輔助程序可能需要回調isapi以完成請求,也就是需要說枚舉伺服器變量。這種情況下,輔助程序将使用同步管道,因為這樣可以保持請求處理邏輯的順序。
完成後,響應被發送到打開了異步管道的aspnet_isapi。現在,請求的狀态變為“done”,之後将從請求表中被删除。如果輔助程序崩潰,正在處理的所有請求仍将保持“executing”狀态并持續一段時間。如果aspnet_isapi檢測到輔助程序已取消,它将自動終止請求并釋放所有相關的iis資源。
以上說明是指預設的asp.net程序模型,即在iis5.x中運作的工作模型。iis6(windowsserver2003提供)的預設工作方式對asp.net程序模型也有影響。當內建在iis6.0中時,asp.net1.1會自動調整自己的工作方式以适應宿主環境。這時,不再需要使用aspnet_wp輔助程序,machine.config檔案中定義的某些配置參數也被忽略。從asp.net的角度來看,iis6的最大改變是有關請求的一切都在aspnet_isapi的控制之下,且都處在w3wp.exe輔助程序的上下文中。輔助程序的帳戶是為web應用程式所屬的應用程式池設定的帳戶。預設情況下,該帳戶是networkservice&#151,它是一個内置的弱帳戶,在功能上與aspnet等價。
輔助程序受一個名為程序回收(recycling)的功能的控制。程序回收具有aspnet_isapi功能,當現有程序消耗的記憶體太多、響應太慢或挂起時可以自動啟動新程序。出現這種情況時,新請求将由新執行個體處理,新執行個體進而變成新的活動程序。但是,指定給舊程序的所有請求仍保持挂起狀态。如果舊程序結束了挂起的請求并進入空閑狀态,該程序即終止。如果輔助程序崩潰,或者由于其他原因停止處理請求,則所有挂起的請求将被重新指定給新程序。
盡管asp.netisapi和輔助程序是asp.net運作時結構的主要組成部分,但還有其他一些可執行檔案也發揮着作用。下表列出了所有這些元件。
表1:構成asp.net運作時環境的可執行檔案
名稱 類型 帳戶 aspnet_isapi.dll win32dll(isapi擴充) localsystem aspnet_wp.exe win32 exeaspnet aspnet_filter.dll win32dll(isapi篩選器) localsystem aspnet_state.exe win32nt serviceaspnet
aspnet_filter.dll元件是一個小的win32isapi篩選器,用來備份asp.net應用程式的無cookie會話狀态。在windowsserver2003中,當啟用iis6程序模型時,aspnet_filter.dll還将篩選出bin目錄中對非可執行資源的請求。
aspnet_state.exe的作用對web應用程式更為重要,因為它用于管理會話狀态。該項服務是可選的,可以用來在web應用程式記憶體空間之外儲存會話狀态資料。該可執行檔案是一種nt服務,既可以在本地運作,也可以遠端運作。當該服務被激活後,可以将asp.net應用程式配置為将所有會話資訊儲存在此程序的記憶體中。一種類似的方案是提供更為可靠的資料存儲方式,不受程序回收和asp.net應用程式故障的影響。該服務在aspnet本地帳戶下運作,但可以使用服務控制管理器(servicecontrolmanager)接口來配置它。
另一個應該介紹的可執行檔案是aspnet_regiis.exe,盡管嚴格來講,它并不屬于asp.net運作時結構。該實用程式可以用來配置環境,以在一台計算機上并行執行不同版本的asp.net,還可用于維修iis和asp.net損壞的配置。該實用程式的工作方式是更新存儲在iis配置資料庫的根目錄和子目錄中的腳本映射。腳本映射是資源類型和asp.net子產品之間的一種關聯關系。最後,還可以使用該工具來顯示已安裝的asp.net版本的狀态,執行其他配置操作,如授予對特定檔案夾的ntfs權限、建立客戶腳本目錄。
webgarden模型
webgarden模型可以通過machine.config檔案中的<processmodel>部分進行配置。請注意,<processmodel>部分是唯一不能放在應用程式特定的web.config檔案中的配置部分。這就是說,webgarden模式可以應用到計算機中運作的所有應用程式。但通過使用machine.config源檔案中的<location>節點,可以針對各個應用程式調節計算機的設定。
<processmodel>部分有兩個屬性可以影響webgarden模型,它們是webgarden和cpumask。webgarden屬性接受布爾值,表示是否使用了多個輔助程序(一個相關的cpu對應一個程序)。預設情況下,該屬性的值為false。cpumask屬性儲存一個dword值,該值的二進制表示為能夠運作asp.net輔助程序的cpu提供了位屏蔽。其預設值為-1(0xffffff),表示可以使用所有可用的cpu。如果webgarden屬性為false,則cpumask屬性的内容将被忽略。cpumask屬性還為正在運作的aspnet_wp.exe的副本數設定了上限。
常言道“閃光的不都是金子”,用在這裡很合适。webgarden模式使得多個輔助程序可以同時運作。但是,需要注意的是所有程序都會有自己的應用程式狀态、程序内會話狀态、asp.net緩存、靜态資料以及運作應用程式所需的其他内容。啟用webgarden模式之後,asp.netisapi将根據cpu的數量盡可能多地啟動輔助程序,每個輔助程序都是下一程序的完整克隆(每一程序都與相應的cpu密切相關)。為平衡工作負荷,傳入的請求以單循環的方式在運作的程序之間進行劃分。輔助程序就象在單處理器中一樣被回收。請注意,asp.net繼承了作業系統中所有的cpu使用限制,并且不包括實作限制的自定義語義。
總之,webgarden模型并不适用于所有應用程式。應用程式的狀态越多,其的性能損失也越多。工作資料存儲在共享記憶體的塊中,以便一個程序輸入的變化可以立即被其他程序得知。但是,處理請求時,工作資料被複制到程序的上下文中。是以,各個輔助程序将處理自己的工作資料,而應用程式的狀态越多,性能損失就越大。鑒于此,仔細、明智的應用程式基準測試是絕對必要的。
隻有重新開機iis後,對配置檔案中<processmodel>部分所做的更改才會生效。在iis6中,webgarden模式的參數儲存在iis配置資料庫中,webgarden和cpumask屬性被忽略。
http管道
asp.netisapi擴充啟動輔助程序後,它将傳遞部分指令行參數。輔助程序使用這些參數來執行加載clr前需要執行的任務。傳遞的值包括:com和dcom安全性所要求的身份驗證等級、可以使用的命名管道的數量和iis程序辨別。命名管道的名稱是使用iis程序辨別和允許的管道數随機生成的。輔助程序不接收可用管道的名稱,但可以接收識别管道名稱所需的資訊。
com和dcom安全性與microsoft?.netframework有何關系?實際上,clr是作為com對象提供的。更準确地說,clr本身不是由com代碼構成的,但是指向clr的接口卻是一個com對象。是以,輔助程序加載clr的方式與加載com對象的方式相同。
當aspx請求遇到iis時,web伺服器将根據選擇的身份驗證模型(匿名、windows、basic或digest)來配置設定一個令牌。當輔助程序收到要處理的請求時,令牌被傳遞到輔助程序。請求由輔助程序中的線程擷取。該線程從最初擷取傳入請求的iis線程繼承身份令牌。在aspnet_wp.exe中,負責處理請求的實際帳戶取決于在特殊的asp.net應用程式中是如何配置模拟的。如果模拟被禁用(預設設定),則線程将在輔助程序的帳戶下運作。預設情況下,該帳戶在asp.net程序模型中為aspnet,在iis6程序模型中為networkservice。這兩個帳戶都是“弱”帳戶,提供的功能比較有限,可以有效抵擋回複性攻擊(revert-to-selfattack)。(回複性攻擊是指将模拟的用戶端的安全性令牌回複到父程序令牌。為輔助程序配置設定弱帳戶可以挫敗此類攻擊。)
高度概括起來,asp.net輔助程序完成的一項主要任務就是将請求交給一系列稱為的http管道的托管對象。要激活http管道,可以建立一個httpruntime類的新執行個體,然後調用其processrequest方法。如前所述,asp.net中始終隻運作一個輔助程序(除非啟用了webgarden模型),該程序在獨立的appdomain中管理所有的web應用程式。每個appdomain都有自己的httpruntime類執行個體,即管道中的輸入點。httpruntime對象初始化一系列有助于實作請求的内部對象。helper對象包括緩存管理器(cache對象)和内部檔案系統螢幕(用于檢測構成應用程式的源檔案的更改)。httpruntime為請求建立上下文,并用與請求相關的http資訊填充上下文。上下文用httpcontext類的執行個體來表示。
另一個在http運作時的設定初期建立的helper對象是文本書寫器,用于包含浏覽器的響應文本。文本書寫器是httpwriter類的執行個體,此對象對頁面代碼以程式設計方式發送的文本進行緩存。http運作時被初始化後,它将查找實作請求的應用程式對象。應用程式對象是httpapplication類的執行個體,該類就是global.asax檔案背後的類。global.asax在程式設計時是可選的,但在建構結構時是必需的。是以,如果應用程式中沒有建構類,則必須使用預設對象。asp.net運作時包括幾個中間工廠類,可以用來查找并傳回有效的handler對象以處理請求。整個過程中用到的第一個工廠類是httpapplicationfactory。它的主要任務是使用url資訊來查找url虛拟目錄和彙集的httpapplication對象之間的比對關系。
應用程式工廠類的行為可以概括為以下幾點:
工廠類維護httpapplication對象池,并使用它們來處理應用程式的請求。池的壽命與應用程式的壽命相同。
應用程式的第一個請求到達時,工廠類提取有關應用程式類型的資訊(global.asax類)、設定用于監視更改的檔案、建立應用程式狀态并觸發application_onstart事件。
工廠類從池中擷取一個httpapplication執行個體,并将要處理的請求放入執行個體中。如果沒有可用的對象,則建立一個新的httpapplication對象。要建立httpapplication對象,需要先完成global.asax應用程式檔案的編譯。
httpapplication開始處理請求,并且隻能在完成這個請求後才能處理新的請求。如果收到來自同一資源的新請求,則由池中的其他對象來處理。
應用程式對象允許所有注冊的http子產品對請求進行預處理,并找出最适合處理請求的處理程式類型。這通過查找請求的url的擴充和配置檔案中的資訊來完成。
http處理程式是一些實作ihttphandler接口的類。.netframework為常見的資源類型提供了一些預定義的處理程式,包括aspx頁面和web服務。machine.config檔案中的<httphandlers>部分定義了httpapplication對象必須執行個體化才能處理特定類型資源的請求的類名。如果helper類是一個處理程式工廠,gethandler方法将确定要使用的處理程式類型。這時,将從一組類似的對象中擷取适當類型的處理程式,并對其進行配置以處理請求。
ihttphandler接口提供了兩個方法:isreusable和processrequest。前者将傳回一個布爾值,表示處理程式是否可以被彙集。(大多數預定義的處理程式都是彙集的,但是您可以自行定義每次都需要新執行個體的處理程式。)processrequest方法包含處理特定類型資源所需的所有邏輯。例如,aspx頁面的處理程式基于以下僞代碼:
privatevoidprocessrequest()
{
//确定請求是否是回發(postback)
ispostback=determinepostbackmode();
//觸發aspx源代碼的page_init事件
pageinit();
//加載viewstate,處理已發送的值。
if(ispostback){
loadpageviewstate();
processpostdata();
}
//觸發aspx源代碼的page_load事件
pageload();
//1)再次處理已發送的值(當
//動态建立控件時)
//2)将屬性更改的伺服器端事件提升為輸入驅動的
//控件(即複選框的狀态改變)
//3)執行與回發事件相關的所有代碼
processpostdatasecondtry();
raisechangedevents();
raisepostbackevent();
//觸發aspx源代碼的page_prerender事件
prerender();
//将控件的目前狀态儲存到viewstate中
savepageviewstate();
//将頁面内容呈現給html
rendercontrol(createhtmltextwriter(response.output));
無論調用的資源類型如何,基于http處理程式的模型是相同的。唯一随資源類型變化而變化的元素是處理程式。httpapplication對象負責查找應該使用哪種處理程式來處理請求。httpapplication對象還負責檢測對動态建立的、表示資源的程式集(如.aspx頁面或.asmxweb服務)所進行的更改。如果檢測到更改,應用程式對象将確定編譯并加載所請求的資源的最新來源。
臨時檔案和頁面程式集
要全面了解asp.nethttp運作時,讓我們來分析一下當請求asp.net頁面時,檔案系統層所發生的變化。接下來,您将了解由http管道的對象管理和監視的一組動态建立的臨時檔案。
雖然可以将頁面的核心代碼隔離在代碼背後的c#或microsoft?visualbasic?.net類中,但可以将web頁面編寫和部署為.aspx文本檔案。對于要顯示為url的頁面來說,.aspx檔案在應用程式的web空間中必須始終可用。.aspx檔案的實際内容将确定應用程式對象要加載的程式集(或多個程式集)。
按照設計,httpapplication對象将查找一個根據請求的aspx檔案命名的類。如果頁面命名為sample.aspx,則要加載的相應的類名為asp.sample_aspx。應用程式對象在web應用程式的所有程式集檔案夾中查找這樣的類,這些檔案夾包括全局程式集緩存(gac)、bin子檔案夾和temporaryasp.netfiles檔案夾。如果未找到這樣的類,http結構将分析.aspx檔案的源代碼,建立一個c#或visualbasic.net類(具體建立哪種類,取決于.aspx頁面上設定的語言),同時對其進行編譯。新建立的程式集的名稱是随機生成的,位于特定于應用程式的子檔案夾中,路徑如下所示:c:/windows/microsoft.net/framework/v1.1.4322/temporaryasp.netfiles。
子檔案夾v1.1.4322特定于asp.net1.1。如果您使用的是asp.net1.0,子檔案夾的版本号會有所不同,即子檔案夾名為v1.0.3705。再次通路頁面時,程式集就已存在,不需要重新建立。但是,httpapplication對象是如何确定特定于頁面的程式集是否存在呢?它每次都要掃描大量檔案夾嗎?不,并不是這樣。
應用程式對象隻檢視temporaryasp.netfiles檔案夾中某個特殊檔案夾的内容。具體路徑(特定于應用程式的路徑)由httpruntime.codegendir屬性傳回。如果是第一次通路.aspx檔案(即還未建立頁面程式集),則該檔案夾中就不存在以aspx頁面名稱開頭的xml檔案。例如,具有動态程式集的sample.aspx頁面應有如下的條目:
sample.aspx.xxxxx.xml
xxxxx占位符是一種散列代碼。通過讀取該xml檔案的内容,應用程式對象就可以了解要加載的程式集的名稱以及要在其中擷取的類。以下代碼片段是這種helper檔案的典型内容。包含asp.sample_aspx類的程式集的名稱是mvxvx8xr。
<preserveassem="mvxvx8xr"type="asp.sample_aspx">
<filedepname="c:/inetpub/wwwroot/vdir/sample.aspx"/>
</preserve>
當然,隻有在分析filedep檔案的源代碼以生成動态程式集時才建立該檔案。對filedep檔案所做的任何更改都會使程式集無效,在下一次請求時必須重新編譯。需要注意的是,在asp.net架構的未來版本中,該實作過程可能會有較大改變。不論什麼原因,隻要您決定在目前應用程式中使用它,都必須十分小心。
由于更新而要為頁面建立新的程式集時,asp.net将驗證是否可以删除舊的程式集。如果舊的程式集隻包含修改後的頁面的類,asp.net将試圖删除并替換該程式集,否則将在保留舊程式集的情況下建立一個新程式集。
在删除過程中,asp.net可能會發現程式集檔案已被加載并鎖定。這種情況下,可以為舊程式集添加一個“.delete”擴充名,以将其重新命名。(注意,所有windows檔案都可以在使用過程中重新命名。)隻要應用程式重新啟動(例如,由于對某個應用程式檔案如global.asax和web.config進行了更改),這些臨時的.delete檔案就将被删除。但在處理下一個請求時,asp.net運作時不會删除這些檔案。
請注意,預設情況下,在整個應用程式重新啟動之前,每個asp.net應用程式最多可以重新編譯15個頁面,同時會損失一些會話和應用程式資料。當最近的編譯次數超過了<httpruntime>部分的numrecompilesbeforeapprestart屬性中設定的門檻值時,将解除安裝appdomain,并重新啟動應用程式。還要注意,在.netframework中,您無法解除安裝單個程式集。appdomain是可以從clr解除安裝的最小的代碼塊。
小結
asp.net應用程式有兩大特征:程序模型和頁面對象模型。asp.net提前使用了iis6.0的一些功能,而iis6.0則是windowsserver2003中提供的全新的、開創性的microsoftweb資訊服務。尤其值得一提的是,在獨立的輔助程序中運作的asp.net應用程式,其行為與iis6中的所有應用程式相同。而且,盡管會出現運作時異常、記憶體洩露或程式錯誤,asp.net運作時仍能自動回收輔助程序以保證實作卓越的性能。這種功能已成為iis6.0的系統功能。
在本文中,我概括介紹了預設的asp.net程序模型的基礎知識,以及iis級代碼(asp.netisapi擴充)和輔助程序之間的互動。同時,還介紹了與iis6程序模型之間的最新差別。