在 ASP.NET 中實(shí)現(xiàn)會話狀態(tài)的基礎(chǔ)
2024-07-10 12:57:10
供稿:網(wǎng)友
在 web 應(yīng)用程序這樣的無狀態(tài)環(huán)境中,了解會話狀態(tài)的概念并沒有實(shí)際的意義。盡管如此,有效的狀態(tài)管理對于大多數(shù) web 應(yīng)用程序來說都是一個必備的功能。microsoft® asp.net 以及許多其他服務(wù)器端編程環(huán)境都提供了一個抽象層,允許應(yīng)用程序基于每個用戶和每個應(yīng)用程序存儲持久性數(shù)據(jù)。
需要特別注意的是,web 應(yīng)用程序的會話狀態(tài)是應(yīng)用程序在不同的請求中緩存和檢索的數(shù)據(jù)。會話表示用戶在與該站點(diǎn)連接期間發(fā)送的所有請求,會話狀態(tài)是用戶在會話期間生成和使用的持久性數(shù)據(jù)的集合。每個會話的狀態(tài)都彼此獨(dú)立,而且在用戶會話結(jié)束時就不復(fù)存在了。
會話狀態(tài)與構(gòu)成 http 協(xié)議和規(guī)范的任何邏輯實(shí)體都沒有對應(yīng)關(guān)系。會話是由服務(wù)器端開發(fā)環(huán)境(例如傳統(tǒng)的 asp 和 asp.net)構(gòu)建的抽象層。asp.net 展示會話狀態(tài)的方式以及會話狀態(tài)的內(nèi)部實(shí)現(xiàn)方式都取決于平臺的基礎(chǔ)結(jié)構(gòu)。因此,傳統(tǒng)的 asp 和 asp.net 以完全不同的方式來實(shí)現(xiàn)會話狀態(tài),預(yù)計在下一版的 asp.net 中會有進(jìn)一步的改進(jìn)和增強(qiáng)。
本文討論如何在 asp.net 1.1 中實(shí)現(xiàn)會話狀態(tài),以及如何在被管理的 web 應(yīng)用程序中優(yōu)化會話狀態(tài)管理。
asp.net 會話狀態(tài)概述
會話狀態(tài)并不是 http 基礎(chǔ)結(jié)構(gòu)的一部分。也就是說,應(yīng)該有一個結(jié)構(gòu)組件將會話狀態(tài)與每個傳入請求綁定在一起。運(yùn)行時環(huán)境(傳統(tǒng)的 asp 或 asp.net)能夠接受 session 之類的關(guān)鍵字,并使用它指示服務(wù)器上存儲的數(shù)據(jù)塊。要成功解析 session 對象的調(diào)用,運(yùn)行時環(huán)境必須將會話狀態(tài)添加到正在處理的請求的調(diào)用上下文中。完成此操作的方式因平臺而異,但它是有狀態(tài) web 應(yīng)用程序的基礎(chǔ)操作。
在傳統(tǒng)的 asp 中,會話狀態(tài)是作為 asp.dll 庫中包含自由線程 com 對象來實(shí)現(xiàn)的。(您對此很好奇嗎?其實(shí)該對象的 clsid 是 d97a6da0-a865-11cf-83af-00a0c90c2bd8。)此對象存儲以名稱/值對集合的方式組織的數(shù)據(jù)。“名稱”占位符表示用來檢索信息的關(guān)鍵字,而“值”占位符表示會話狀態(tài)中存儲的內(nèi)容。名稱/值對按照會話 id 進(jìn)行分組,這樣,每個用戶看到的只是他/她自己創(chuàng)建的名稱/值對。
在 asp.net 中,會話狀態(tài)的編程接口與傳統(tǒng)的 asp 幾乎是相同的。但它們的基礎(chǔ)實(shí)現(xiàn)是完全不同的,前者比后者更具有靈活性、可擴(kuò)展性和更強(qiáng)的編程功能。深入研究 asp.net 會話狀態(tài)之前,讓我們簡單回顧一下 asp.net 會話基礎(chǔ)結(jié)構(gòu)的某些結(jié)構(gòu)功能。
在 asp.net 中,任何傳入 http 請求都要通過 http 模塊管道進(jìn)行傳輸。每個模塊都可以篩選并修改請求所攜帶的大量信息。與每個請求關(guān)聯(lián)的信息叫做“調(diào)用上下文”,編程中用 httpcontext 對象來表示。我們不應(yīng)將請求的上下文視為狀態(tài)信息的另一個容器,雖然它提供的 items 集合只是一個數(shù)據(jù)容器。httpcontext 對象不同于所有其他狀態(tài)對象(例如,session、application 和 cache),因?yàn)樗挠邢奚芷诔隽颂幚碚埱笏璧臅r間。當(dāng)請求通過一系列注冊的 http 模塊后,其 httpcontext 對象將包含狀態(tài)對象的引用。當(dāng)最終可以處理請求時,關(guān)聯(lián)的調(diào)用上下文將綁定到特定會話 (session) 和全局狀態(tài)對象(application 和 cache)。
負(fù)責(zé)設(shè)置每個用戶的會話狀態(tài)的 http 模塊為 sessionstatemodule。該模塊的結(jié)構(gòu)是根據(jù) ihttpmodule 接口設(shè)計的,它為 asp.net 應(yīng)用程序提供大量與會話狀態(tài)有關(guān)的服務(wù)。包括生成會話 id、cookieless 會話管理、從外部狀態(tài)提供程序中檢索會話數(shù)據(jù)以及將數(shù)據(jù)綁定到請求的調(diào)用上下文。
http 模塊并不在內(nèi)部存儲會話數(shù)據(jù)。會話狀態(tài)始終保存在名為“狀態(tài)提供程序”的外部組件中。狀態(tài)提供程序完全封裝會話狀態(tài)數(shù)據(jù),并通過 istateclientmanager 接口的方法與其他部分進(jìn)行通信。會話狀態(tài) http 模塊調(diào)用該接口上的方法來讀取并保存會話狀態(tài)。asp.net 1.1 支持三種不同的狀態(tài)提供程序,如表 1 所示。
表 1:狀態(tài)客戶端提供程序
提供程序 說明
inproc 會話值在 asp.net 輔助進(jìn)程(microsoft® windows server™ 2003 中的 aspnet_wp.exe 或 w3wp.exe)的內(nèi)存中保持為活動對象。這是默認(rèn)選項(xiàng)。
stateserver 會話值被序列化并存儲在單獨(dú)進(jìn)程 (aspnet_state.exe) 的內(nèi)存中。該進(jìn)程還可以在其他計算機(jī)上運(yùn)行。
sqlserver 會話值被序列化并存儲在 microsoft® sql server™ 表中。sql server 的實(shí)例可以在本地運(yùn)行,也可以遠(yuǎn)程運(yùn)行。
會話狀態(tài) http 模塊將從 web.config 文件的 <sessionstate> 部分讀取當(dāng)前選定的狀態(tài)提供程序。
<sessionstate mode="inproc | stateserver | sqlserver />
根據(jù) mode 特性的值,將通過不同的步驟從不同的進(jìn)程中檢索會話狀態(tài)并將其存儲到不同的進(jìn)程中。默認(rèn)情況下,會話狀態(tài)存儲在本地的 asp.net 輔助進(jìn)程中。特殊情況下,會將其存儲在 asp.net cache 對象的專用槽中(不能通過編程方式訪問)。也可以將會話狀態(tài)存儲在外部,甚至是遠(yuǎn)程進(jìn)程中(例如,名為 aspnet_state.exe 的 windows nt 服務(wù)中)。第三個選項(xiàng)是將會話狀態(tài)存儲到由 sql server 2000 管理的專用數(shù)據(jù)庫表中。
http 模塊會在請求的一開始對會話值進(jìn)行反序列化,使它們成為詞典對象。然后,將采用編程方式通過類(例如,httpcontext 和 page)顯示的屬性 session 來訪問詞典(實(shí)際上是 httpsessionstate 類型的對象)。會話狀態(tài)值與開發(fā)人員可見的會話對象之間的綁定將持續(xù)到請求結(jié)束。如果請求成功完成,所有狀態(tài)值將被序列化回狀態(tài)提供程序,并可用于其他請求。
圖 1 說明了請求的 asp.net 頁面與會話值之間的通信。每個頁面所使用的代碼都與 page 類上的 session 屬性有聯(lián)系。其編程方式與傳統(tǒng)的 asp 幾乎相同。
圖 1:asp.net 1.1 中的會話狀態(tài)體系結(jié)構(gòu)
在完成請求所需的時間內(nèi),會話狀態(tài)的物理值處于鎖定狀態(tài)。該鎖定由 http 模塊在內(nèi)部管理并用于同步對會話狀態(tài)的訪問。
會話狀態(tài)模塊實(shí)例化應(yīng)用程序的狀態(tài)提供程序,并使用從 web.config 文件中讀取的信息對其進(jìn)行初始化。接下來,每個提供程序?qū)⒗^續(xù)自己的初始化操作。提供程序的類型不同,其初始化操作會大不相同。例如,sql server 狀態(tài)管理器將打開與給定數(shù)據(jù)庫的連接,而進(jìn)程外管理器將檢查指定的 tcp 端口。另一方面,inproc 狀態(tài)管理器將存儲對回調(diào)函數(shù)的引用。從緩存中刪除元素時將執(zhí)行此操作,并用于觸發(fā)應(yīng)用程序的 session_onend 事件。
同步訪問會話狀態(tài)
當(dāng) web 頁對 session 屬性進(jìn)行非常簡單且直觀的調(diào)用時,究竟會出現(xiàn)什么情況呢?許多操作都是在后臺進(jìn)行的,如下面的繁瑣代碼所示:
int sitecount = convert.toint32(session["counter"]);
上述代碼實(shí)際上訪問的是 http 模塊創(chuàng)建的會話值在本地內(nèi)存中的副本,從特定狀態(tài)提供程序(參見圖 1)中讀取數(shù)據(jù)。如果其他頁面也試圖同步訪問該會話狀態(tài),又會如何呢?這種情況下,當(dāng)前的請求可能會停止處理不一致的數(shù)據(jù)或過時的數(shù)據(jù)。為了避免這種情況,會話狀態(tài)模塊將實(shí)現(xiàn)一個讀取器/寫入器鎖定機(jī)制,并對狀態(tài)值的訪問進(jìn)行排隊(duì)。對會話狀態(tài)具有寫入權(quán)限的頁面將保留該會話的寫入器鎖定,直到請求終止。
通過將 @page 指令的 enablesessionstate 屬性設(shè)置為 true,頁面可以請求會話狀態(tài)的寫入權(quán)限。(這是默認(rèn)設(shè)置)。但是,頁面還可以擁有會話狀態(tài)的只讀權(quán)限,例如,當(dāng) enablesessionstate 屬性被設(shè)置為 readonly 時。在這種情況下,模塊將保留該會話的讀取器鎖定,直到該頁面的請求結(jié)束。結(jié)果將發(fā)生并發(fā)讀取。
如果頁面請求設(shè)置一個讀取器鎖定,同一會話中同時處理的其他請求將無法更新會話狀態(tài),但是至少可以進(jìn)行讀取。也就是說,如果當(dāng)前正在處理會話的只讀請求,那么等候的只讀請求要比需要完全訪問權(quán)限的請求具有更高的優(yōu)先權(quán)。如果頁面請求為會話狀態(tài)設(shè)置一個寫入器鎖定,那么所有其他頁面都將被阻止,無論它們是否要讀取或?qū)懭雰?nèi)容。例如,如果同時有兩個框架試圖在 session 中寫入內(nèi)容,一個框架必須等到另一個框架完成后才能寫入。
比較狀態(tài)提供程序
默認(rèn)情況下,asp.net 應(yīng)用程序?qū)挔顟B(tài)存儲在輔助進(jìn)程的內(nèi)存中,特別是 cache 對象的專用槽中。選中 inproc 模式時,會話狀態(tài)將存儲在 cache 對象內(nèi)的槽中。此槽被標(biāo)記為專用槽,無法通過編程方式進(jìn)行訪問。換句話說,如果枚舉 asp.net 數(shù)據(jù)緩存中的所有項(xiàng)目,將不會返回類似于給定會話狀態(tài)的任何對象。cache 對象提供兩類槽:專用槽和公用槽。編程人員可以添加和處理公用槽,但專用槽只能由系統(tǒng)(特別是 system.web 部件中定義的類)專用。
每個活動會話的狀態(tài)都占用緩存中的一個專用槽。槽的名稱根據(jù)會話 id 進(jìn)行命名,其值是名為 sessionstateitem 的內(nèi)部未聲明類的一個實(shí)例。inproc 狀態(tài)提供程序獲取會話 id 并在緩存中檢索對應(yīng)的元素。然后將 sessionstateitem 對象的內(nèi)容輸入 httpsessionstate 詞典對象,并由應(yīng)用程序通過 session 屬性進(jìn)行訪問。請注意,asp.net 1.0 中存在一個錯誤,使 cache 對象的專用槽可以通過編程方式進(jìn)行枚舉。如果您在 asp.net 1.0 下運(yùn)行以下代碼,則能夠枚舉與每個當(dāng)前活動會話狀態(tài)中包含的對象對應(yīng)的項(xiàng)目。
foreach(dictionaryentry elem in cache)
{
response.write(elem.key + ": " + elem.value.tostring());
}
此錯誤已經(jīng)在 asp.net 1.1 中得到解決,當(dāng)您枚舉緩存的內(nèi)容時,將不再列出任何系統(tǒng)槽。
到目前為止,inproc 可能是最快的訪問選項(xiàng)。但請記住,會話中存儲的數(shù)據(jù)越多,web 服務(wù)器所消耗的內(nèi)存就越多,這樣會潛在地增加性能降低的風(fēng)險。如果您計劃使用任何進(jìn)程外解決方案,應(yīng)該認(rèn)真考慮一下序列化和反序列化可能帶來的影響。進(jìn)程外解決方案使用 windows nt 服務(wù) (aspnet_state.exe) 或 sql server 表來存儲會話值。因此,會話狀態(tài)保留在 asp.net 輔助進(jìn)程之外,并且需要使用額外的代碼層,在會話狀態(tài)和實(shí)際的存儲介質(zhì)之間進(jìn)行序列化和反序列化操作。只要處理請求就會發(fā)生此操作,而且隨后必須對其進(jìn)行最高程度的優(yōu)化。
因?yàn)樾枰獙挃?shù)據(jù)從外部儲備庫復(fù)制到本地會話詞典中,所以請求導(dǎo)致性能下降了 15%(進(jìn)程外)到 25% (sql server)。請注意,雖然這只是一種粗略的估計,但它應(yīng)該接近于最低程度的影響,最高程度的影響將遠(yuǎn)高于此。實(shí)際上,這種估計并沒有完全考慮到會話狀態(tài)中實(shí)際保存的類型的復(fù)雜程度。
在進(jìn)程外存儲方案中,會話狀態(tài)存活的時間較長,使應(yīng)用程序的功能更強(qiáng)大,因?yàn)樗梢苑乐?microsoft® internet 信息服務(wù) (iis) 和 asp.net 失敗。通過將會話狀態(tài)與應(yīng)用程序相分離,您還可以更容易地將現(xiàn)有應(yīng)用程序擴(kuò)展到 web farm 和 web garden 體系結(jié)構(gòu)中。另外,會話狀態(tài)存儲在外部進(jìn)程中,從根本上消除了由于進(jìn)程循環(huán)而導(dǎo)致的周期性數(shù)據(jù)丟失的風(fēng)險。
下面介紹如何使用 windows nt 服務(wù)。正如上文所述,nt 服務(wù)是一個名為 aspnet_state.exe 的進(jìn)程,通常位于 c:/winnt/microsoft.net/framework/v1.1.4322 文件夾中。
實(shí)際目錄取決于您實(shí)際運(yùn)行的 microsoft® .net framework 版本。使用狀態(tài)服務(wù)器之前,應(yīng)確保該服務(wù)就緒并正運(yùn)行在用作會話存儲設(shè)備的本地或遠(yuǎn)程計算機(jī)上。狀態(tài)服務(wù)是 asp.net 的組成部分并與之一起安裝,因此您無需運(yùn)行其他安裝程序。默認(rèn)情況下,狀態(tài)服務(wù)并沒有運(yùn)行,需要手動啟動。asp.net 應(yīng)用程序?qū)⒃诩虞d狀態(tài)服務(wù)器之后立即嘗試與之建立連接。因此,該服務(wù)必須準(zhǔn)備就緒且正在運(yùn)行,否則將引發(fā) http 異常。下圖顯示了該服務(wù)的屬性對話框。
圖 2:asp.net 狀態(tài)服務(wù)器的屬性對話框
asp.net 應(yīng)用程序需要指定會話狀態(tài)服務(wù)所在的計算機(jī)的 tcp/ip 地址。必須將以下設(shè)置輸入該應(yīng)用程序的 web.config 文件中。
<configuration>
<system.web>
<sessionstate
mode="stateserver"
stateconnectionstring="tcpip=expoware:42424" />
</system.web>
</configuration>
stateconnectionstring 特性包含計算機(jī)的 ip 地址以及用來進(jìn)行數(shù)據(jù)交換的端口。默認(rèn)的計算機(jī)地址為 127.0.0.1(本地主機(jī)),默認(rèn)端口為 42424。您也可以按名稱指示計算機(jī)。對于代碼來說,使用本地或遠(yuǎn)程計算機(jī)是完全透明的。請注意,不能在該名稱中使用非 ascii 字符,并且端口號是強(qiáng)制的。
如果您使用進(jìn)程外會話存儲,會話狀態(tài)將仍然存在并且可供將來使用,無論 asp.net 輔助進(jìn)程出現(xiàn)何種情況。如果該服務(wù)被中斷,數(shù)據(jù)將被保留下來,并且在該服務(wù)恢復(fù)時自動進(jìn)行檢索。但是,如果狀態(tài)提供程序服務(wù)停止或失敗,數(shù)據(jù)將丟失。如果您希望應(yīng)用程序具有強(qiáng)大的功能,請使用 sqlserver 模式,而不要使用 stateserver 模式。
<configuration>
<system.web>
<sessionstate
mode="sqlserver"
sqlconnectionstring="server=127.0.0.1;uid=<user id>;pwd=<password>;" />
</system.web>
</configuration>
您可以通過 sqlconnectionstring 特性指定連接字符串。請注意,特性字符串必須包含用戶 id、密碼和服務(wù)器名稱。它不能包含 database 和 initial catalog 之類的標(biāo)記,因?yàn)榇诵畔⒛J(rèn)為固定名稱。用戶 id 和密碼可以替換為集成的安全設(shè)置。
如何創(chuàng)建數(shù)據(jù)庫?asp.net 提供兩對腳本來配置數(shù)據(jù)庫環(huán)境。第一對腳本名為 installsqlstate.sql 和 uninstallsqlstate.sql,與會話狀態(tài) nt 服務(wù)位于同一個文件夾中。它們創(chuàng)建名為 aspstate 的數(shù)據(jù)庫和幾個存儲的過程。但是,數(shù)據(jù)存儲在 sql server 臨時存儲區(qū)域 tempdb 數(shù)據(jù)庫中。這意味著,如果重新啟動 sql server 計算機(jī),會話數(shù)據(jù)將丟失。
要解決這一局限性,請使用第二對腳本。第二對腳本名為 installpersistsqlstate.sql 和 uninstallpersistsqlstate.sql。在這種情況下,將創(chuàng)建 aspstate 數(shù)據(jù)庫,但是會在同一個數(shù)據(jù)庫中創(chuàng)建數(shù)據(jù)表,而且這些數(shù)據(jù)表同樣是持久的。為會話安裝 sql server 支持時,還將創(chuàng)建一個作業(yè),以刪除會話狀態(tài)數(shù)據(jù)庫中過期的會話。該作業(yè)名為 aspstate_job_deleteexpiredsessions 并且一直運(yùn)行。請注意,要使該作業(yè)正常進(jìn)行,需要運(yùn)行 sqlserveragent 服務(wù)。
無論您選擇哪種模式,為會話狀態(tài)操作進(jìn)行編碼的方式都不會改變。您可以始終針對 session 屬性進(jìn)行工作并像平常一樣讀取和寫入值。所有行為上的差異都是在較低的抽象層上處理的。狀態(tài)序列化或許是會話模式之間的最重要差異。
狀態(tài)序列化和反序列化
使用進(jìn)程內(nèi)模式時,對象作為各自類的活動實(shí)例存儲在會話狀態(tài)中。如果未發(fā)生真正的序列化和反序列化,則表示您實(shí)際上可以在 session 中存儲您創(chuàng)建的任何對象(包括無法序列化的對象和 com 對象),并且訪問它們的開銷也不會太高。如果您選擇進(jìn)程外狀態(tài)提供程序,又是另外一種情況。
在進(jìn)程外體系結(jié)構(gòu)中,會話值將從本地存儲介質(zhì)(外部 appdomain 數(shù)據(jù)庫)復(fù)制到處理請求的 appdomain 的內(nèi)存中。需要使用序列化/反序列化圖層完成該任務(wù),并表示進(jìn)程外狀態(tài)提供程序的某項(xiàng)主要成本。這種情況對代碼產(chǎn)生的主要影響是只能在會話詞典中存儲可序列化的對象。
根據(jù)所涉及的數(shù)據(jù)類型,asp.net 使用兩種方法對數(shù)據(jù)進(jìn)行序列化和反序列化。對于基本類型,asp.net 使用經(jīng)過優(yōu)化的內(nèi)部序列化程序;對于其他類型(包括對象和用戶定義的類),asp.net 使用 .net 二進(jìn)制格式化程序。基本類型包括字符串、日期時間、布爾值、字節(jié)、字符以及所有的數(shù)字類型。對于這些類型,使用量身制作的序列化程序要比使用默認(rèn)的常用 .net 二進(jìn)制格式化程序更快。
經(jīng)過優(yōu)化的序列化程序沒有公開發(fā)布,也沒有以文檔形式提供。它僅僅是二進(jìn)制讀取器/寫入器,并且使用簡單但有效的存儲架構(gòu)。該序列化程序使用 binarywriter 類寫入一個字節(jié)表示類型,然后寫入一個字節(jié)表示該類型對應(yīng)的值。讀取序列化的字節(jié)時,該類首先提取一個字節(jié),檢測要讀取的數(shù)據(jù)類型,然后對 binaryreader 類調(diào)用特定類型的 readxxx 方法。
請注意,布爾值和數(shù)字類型的大小是眾所周知的,但對字符串并非如此。在基礎(chǔ)數(shù)據(jù)流上,字符串始終帶有一個固定長度的前綴(一次編寫 7 位整數(shù)代碼),讀取器根據(jù)這一事實(shí)來確定字符串的正確大小。而日期值是通過只寫入構(gòu)成日期的標(biāo)記總數(shù)來保存的。因此,要對會話執(zhí)行序列化操作,日期應(yīng)為 int64 類型。
只要將包含的類標(biāo)記為可序列化的類,便可以使用 binaryformatter 類對更復(fù)雜的對象(以及自定義對象)執(zhí)行序列化操作。所有非基本類型都采用相同的類型 id 進(jìn)行標(biāo)識并與基本類型存儲在同一個數(shù)據(jù)流中。總之,序列化操作會導(dǎo)致性能下降 15% 至 25%。但請注意,這是基于假定使用基本類型所進(jìn)行的粗略估計。使用的類型越復(fù)雜,開銷越大。
如果不大量使用基本類型,很難實(shí)現(xiàn)有效的會話數(shù)據(jù)存儲。因此,至少在理論上,使用三個會話槽保存對象的三個不同的字符串屬性要比對整個對象進(jìn)行序列化好。但是,如果要序列化的對象包含 100 個屬性,那該怎么辦呢?是要使用 100 個槽,還是只使用一個槽?在許多情況下,更好的方法是將復(fù)雜的類型轉(zhuǎn)換為多個簡單的類型。這種方法基于類型轉(zhuǎn)換器。“類型轉(zhuǎn)換器”是一種輕便的序列化程序,它以字符串集合的形式返回類型的關(guān)鍵屬性。類型轉(zhuǎn)換器是使用特性與基類綁定在一起的外部類。由類型編寫者決定保存哪些屬性以及如何保存。類型轉(zhuǎn)換器對于 viewstate 存儲也有幫助,它代表的是比二進(jìn)制格式化程序更有效的會話存儲方法。
會話的生命周期
關(guān)于 asp.net 會話管理,重要的一點(diǎn)是,僅當(dāng)將第一個項(xiàng)目添加到內(nèi)存詞典中時,會話狀態(tài)對象的生命周期才開始。僅在執(zhí)行如下代碼片斷后,才可以認(rèn)為 asp.net 會話開始。
session["myslot"] = "some data";
session 詞典通常包含 object 類型,要向后讀取數(shù)據(jù),需要將返回的值轉(zhuǎn)換為更具體的類型。
string data = (string) session["myslot"];
當(dāng)頁面將數(shù)據(jù)保存到 session 中時,會將值加載到 httpsessionstate 類包含的特制的詞典類中。完成當(dāng)前處理的請求時,會將詞典的內(nèi)容加載到狀態(tài)提供程序中。如果由于未通過編程方式將數(shù)據(jù)放入詞典而導(dǎo)致會話狀態(tài)為空,則不會將數(shù)據(jù)序列化到存儲介質(zhì)中,而且更重要的是,不會在 asp.net cache、sql server 或 nt 狀態(tài)服務(wù)中創(chuàng)建槽來跟蹤當(dāng)前會話。這是出于性能方面的原因,但會對處理會話 id 的方式產(chǎn)生重要影響:將為每個請求生成一個新的會話 id,直到將某些數(shù)據(jù)存儲到會話詞典中。
需要將會話狀態(tài)與正在處理的請求連接時,http 模塊會檢索會話 id(如果它不是啟動請求),并在配置的狀態(tài)提供程序中尋找它。如果沒有返回數(shù)據(jù),http 模塊將為請求生成一個新的會話 id。這可以很容易地通過以下頁面進(jìn)行測試:
<%@ page language="c#" trace="true" %>
</html>
<body>
<form runat="server">
<asp:button runat="server" text="click" />
</form>
</body>
</html>
無論何時單擊該按鈕并返回頁面,都將生成新的會話 id,同時記錄跟蹤信息。
圖 3:在沒有將數(shù)據(jù)存儲到會話詞典中的應(yīng)用程序中,為每個請求生成一個新的會話 id。
session_onstart 事件的情況如何呢?也會為每個請求引發(fā)該事件嗎?如果應(yīng)用程序定義 session_onstart 處理程序,則會始終保存會話狀態(tài),即使會話狀態(tài)為空。因此,對于第一個請求之后的所有請求來說,會話 id 始終為常量。僅在確實(shí)必要時,才使用 session_onstart 處理程序。
如果會話超時或被放棄,下次訪問無狀態(tài)應(yīng)用程序時,其會話 id 不會發(fā)生改變。經(jīng)過設(shè)計后,即使會話狀態(tài)過期,會話 id 也能持續(xù)到瀏覽器會話結(jié)束。也就是說,只要瀏覽器實(shí)例相同,就始終使用同一個會話 id 表示多個會話。
session_onend 事件標(biāo)志著會話的結(jié)束,并用于執(zhí)行終止該會話所需的所有清除代碼。但請注意,只有 inproc 模式支持該事件,也就是說,只有將會話數(shù)據(jù)存儲在 asp.net 輔助進(jìn)程中時才支持該事件。對于要引發(fā)的 session_onend 事件來說,必須首先存在會話狀態(tài),這意味著必須在該會話狀態(tài)中存儲一些數(shù)據(jù),并且必須至少完成一個請求。
在 inproc 模式下,作為項(xiàng)目添加到緩存中的會話狀態(tài)被賦予一個可變過期時間策略。可變過期時間表示如果某個項(xiàng)目在一定時間內(nèi)沒有使用,將被刪除。在此期間處理的任何請求的過期時間都將被重置。會話狀態(tài)項(xiàng)目的時間間隔被設(shè)置為會話超時。用來重置會話狀態(tài)過期時間的技術(shù)非常簡單和直觀:會話 http 模塊只讀取 asp.net cache 中存儲的會話狀態(tài)項(xiàng)目。如果知道 asp.net cache 對象的內(nèi)部結(jié)構(gòu),該模塊將進(jìn)行計算以重新設(shè)置可變過期時間。因此,當(dāng)緩存項(xiàng)目過期時,會話已超時。
過期的項(xiàng)目將自動從緩存中刪除。狀態(tài)會話模塊作為此項(xiàng)目的過期時間策略的一部分,也代表了一個刪除回調(diào)函數(shù)。緩存將自動調(diào)用刪除函數(shù),刪除函數(shù)然后將引發(fā) session_onend 事件。如果應(yīng)用程序通過進(jìn)程外組件來執(zhí)行會話管理,則永遠(yuǎn)不會引發(fā)結(jié)束事件。
cookieless 會話
每個活動 asp.net 會話都是使用僅由 url 允許的字符組成的 120 位字符串標(biāo)識的。會話 id 是使用隨機(jī)數(shù)生成器 (rng) 加密提供程序生成的。該服務(wù)提供程序返回一個包含 15 個隨機(jī)生成數(shù)的序列(15 字節(jié) x 8 位 = 120 位)。隨機(jī)數(shù)數(shù)組然后被映射到有效的 url 字符并以字符串形式返回。
會話 id 字符串被發(fā)送到瀏覽器,然后通過以下兩種方式之一返回服務(wù)器應(yīng)用程序:使用 cookie(就像在傳統(tǒng) asp 中一樣)或經(jīng)過修改的 url。默認(rèn)情況下,會話狀態(tài)模塊將在客戶端創(chuàng)建 http cookie,但是可以使用嵌入會話 id 字符串的修改后的 url(特別是對于不支持 cookie 的瀏覽器)。采用哪種方法取決于應(yīng)用程序的 web.config 文件中所存儲的配置設(shè)置。要配置會話設(shè)置,可以使用 <sessionstate> 區(qū)段和 cookieless 特性。
<sessionstate cookieless="true|false" />
默認(rèn)情況下,cookieless 特性為 false,表示使用了 cookie。實(shí)際上,cookie 只是 web 頁放在客戶端硬盤上的一個文本文件。在 asp.net 中,cookie 由 httpcookie 類的一個實(shí)例來表示。通常,cookie 包含名稱、值集合和過期時間。cookieless 特性被設(shè)置為 false 時,會話狀態(tài)模塊實(shí)際上將創(chuàng)建一個名為 asp.net_sessionid 的 cookie 并將會話 id 存儲在其中。下面的偽代碼顯示了創(chuàng)建 cookie 的過程:
httpcookie sessioncookie;
sessioncookie = new httpcookie("asp.net_sessionid", sessionid);
sessioncookie.path = "/";
會話 cookie 的過期時間很短,在每個請求成功后更新過期時間。cookie 的 expires 屬性表示 cookie 在客戶端的過期時間。如果未顯式設(shè)置會話 cookie,expires 屬性將默認(rèn)為 datetime.minvalue,即 .net framework 允許的最小時間單位。
要禁用會話 cookie,請?jiān)谂渲梦募袑?cookieless 特性設(shè)置為 true,如下所示:
<configuration>
<system.web>
<sessionstate cookieless="true" />
</system.web>
</configuration>
此時,假設(shè)您請求以下 url 處的頁面:
http://www.contoso.com/sample.aspx
瀏覽器地址欄中實(shí)際顯示的內(nèi)容會有所不同,現(xiàn)在包含會話 id,如下所示:
http://www.contoso.com/(5ylg0455mrvws1uz5mmaau45)/sample.aspx
實(shí)例化會話狀態(tài) http 模塊時,該模塊將檢查 cookieless 特性的值。如果為 true,則將請求重定向 (http 302) 到經(jīng)過修改的包含會話 id(緊跟在頁面名稱前)的虛擬 url。再次處理請求時,請求中會包含該會話 id。如果請求啟動新的會話,http 模塊將生成新的會話 id,然后重定向該請求。如果回傳請求,則會話 id 已經(jīng)存在,因?yàn)榛貍魇褂孟鄬?url。
使用 cookieless 會話的缺點(diǎn)是,如果調(diào)用絕對 url,將丟失會話狀態(tài)。使用 cookie 時,您可以清除地址欄,轉(zhuǎn)至其他應(yīng)用程序,然后返回上一個應(yīng)用程序并檢索相同的會話值。如果在禁用會話 cookie 時執(zhí)行此操作,將丟失會話數(shù)據(jù)。例如,以下代碼將打斷該會話:
<a runat="server" href="/code/page.aspx">click</a>
如果需要使用絕對 url,請通過一些小技巧手動將會話 id 添加到 url 中。您可以對 httpresponse 類調(diào)用 applyapppathmodifier 方法。
<a runat="server"
href=<% =response.applyapppathmodifier("/code/page.aspx")%> >click</a>
applyapppathmodifier 方法將使用表示 url 的字符串,并返回嵌入會話信息的絕對 url。例如,需要從 http 頁面重定向到 https 頁面時,此技巧特別有用。
小結(jié)
會話狀態(tài)最初由傳統(tǒng)的 asp 引入,它是基于詞典的 api,使開發(fā)人員能夠存儲會話期間的自定義數(shù)據(jù)。在 asp.net 中,會話狀態(tài)支持以下兩種主要功能:cookieless 會話 id 存儲和傳輸,以及會話數(shù)據(jù)實(shí)際存儲的狀態(tài)提供程序。為實(shí)現(xiàn)這兩種新功能,asp.net 利用 http 模塊控制會話狀態(tài)與正在處理的請求上下文之間的綁定。
在傳統(tǒng)的 asp 中,使用會話狀態(tài)就是指使用 cookie。在 asp.net 中已不再如此,因?yàn)榭梢允褂?cookieless 架構(gòu)。借助 http 模塊的力量,可以分解請求的 url 以使其包含會話 id,然后將其重定向。接下來,http 模塊會從該 url 中提取會話 id 并使用它檢索任何存儲的狀態(tài)。
會話的物理狀態(tài)可以存儲在三個位置:進(jìn)程內(nèi)內(nèi)存、進(jìn)程外內(nèi)存和 sql server 表。數(shù)據(jù)必須經(jīng)過序列化/反序列化處理,才能供應(yīng)用程序使用。http 模塊會在請求開始時將會話值從提供程序復(fù)制到應(yīng)用程序的內(nèi)存中。請求完成后,修改后的狀態(tài)將返回提供程序。這種數(shù)據(jù)通信會對性能產(chǎn)生不同程度的不利影響,但是會大大增強(qiáng)可靠性和穩(wěn)定性,也使對 web farm 和 web garden 體系結(jié)構(gòu)的支持更容易實(shí)現(xiàn)。