ASP.NET 中 Session 實現(xiàn)原理淺析 [1] 會話的建立流程
2024-07-10 12:56:21
供稿:網(wǎng)友
 
 
http 協(xié)議之所以能夠獲得如此大的成功,其設(shè)計實現(xiàn)的簡潔性和無狀態(tài)連接的高效率是很重要的原因。而為了在無狀態(tài)的 http 請求和有狀態(tài)的客戶端操作之間達(dá)到平衡,產(chǎn)生了服務(wù)器端會話 (session) 的概念。客戶端在連接到服務(wù)器后,就由 web 服務(wù)器產(chǎn)生并維護(hù)一個客戶端的會話;當(dāng)客戶端通過無狀態(tài) http 協(xié)議再次連接到服務(wù)器時,服務(wù)器根據(jù)客戶端提交的某種憑據(jù),如 cookie 或 url 參數(shù),將客戶關(guān)聯(lián)到某個會話上。這種思路在各種開發(fā)語言和開發(fā)環(huán)境中大量得到應(yīng)用。 
在 asp.net 中,web 應(yīng)用程序和會話狀態(tài)被分別進(jìn)行維護(hù),通過 httpapplication 和 httpsessionstate 分離 web 應(yīng)用程序與會話的功能。應(yīng)用程序?qū)舆壿嬙?global.asax 文件中實現(xiàn),運行時編譯成 system.web.httpapplication 的實例;會話則作為單獨的 system.web.sessionstate.httpsessionstate 實例,由服務(wù)器統(tǒng)一為每個用戶會話維護(hù),通過 asp.net 頁面編譯成的 system.web.ui.page 對象子類的 session 屬性訪問。關(guān)于 asp.net 中不同層次關(guān)系可參考我以前的一篇文章《.net 1.1中預(yù)編譯asp.net頁面實現(xiàn)原理淺析 [1] 自動預(yù)編譯機制淺析》,以下簡稱【文1】。 
asp.net 在處理客戶端請求時,首先將根據(jù)客戶端環(huán)境,生成一個 system.web.httpcontext 對象,并將此對象作為執(zhí)行上下文傳遞給后面的頁面執(zhí)行代碼。 
在【文1】的分析中我們可以看到,httpruntime 在處理頁面請求之前,根據(jù) httpworkerrequest 中給出的環(huán)境,構(gòu)造 httpcontext 對象,并以次對象作為參數(shù)從應(yīng)用程序池中獲取可用應(yīng)用程序。簡要代碼如下: 
以下內(nèi)容為程序代碼: 
private void httpruntime.processrequestinternal(httpworkerrequest wr) 
{ 
// 構(gòu)造 http 調(diào)用上下文對象 
httpcontext ctxt = new httpcontext(wr, 0); 
//... 
// 獲取當(dāng)前 web 應(yīng)用程序?qū)嵗?
ihttphandler handler = httpapplicationfactory.getapplicationinstance(ctxt); 
// 調(diào)用 handler 實際處理頁面請求 
} 
httpapplicationfactory 工廠內(nèi)部維護(hù)了一個可用的應(yīng)用程序?qū)嵗彌_池,用戶降低應(yīng)用程序?qū)ο髽?gòu)造的負(fù)荷。 
如果池中沒有可用的應(yīng)用程序?qū)ο髮嵗藢ο蠊S最終會調(diào)用 system.web.httpruntime.createnonpublicinstance 方法構(gòu)造新的應(yīng)用程序?qū)嵗⒄{(diào)用其 initinternal 方法初始化。詳細(xì)步驟分析見【文1】 
以下內(nèi)容為程序代碼: 
internal static ihttphandler httpapplicationfactory.getapplicationinstance(httpcontext ctxt) 
{ 
// 處理定制應(yīng)用程序 
//... 
// 處理調(diào)試請求 
//... 
// 判斷是否需要初始化當(dāng)前 httpapplicationfactory 實例 
//... 
// 獲取 web 應(yīng)用程序?qū)嵗?
return httpapplicationfactory._theapplicationfactory.getnormalapplicationinstance(ctxt); 
} 
private httpapplication httpapplicationfactory.getnormalapplicationinstance(httpcontext context) 
{ 
httpapplication app = null; 
// 嘗試從已施放的 web 應(yīng)用程序?qū)嵗犃兄蝎@取 
//... 
if(app == null) 
{ 
// 構(gòu)造新的 web 應(yīng)用程序?qū)嵗?
app = (httpapplication)system.web.httpruntime.createnonpublicinstance(this._theapplicationtype); 
// 初始化 web 應(yīng)用程序?qū)嵗?
app.initinternal(context, this._state, this._eventhandlermethods); 
} 
return app; 
} 
這里的 system.web.httpapplication.initinternal 函數(shù)完成對應(yīng)用程序?qū)ο蟮某跏蓟ぷ鳎ㄕ{(diào)用 httpapplication.initmodules 函數(shù)初始化 http 模塊(后面將詳細(xì)介紹),并將作為參數(shù)傳入的 httpcontext 實例保存到 httpapplication._context 字段中。而此 http 上下文對象將被后面用于獲取會話對象。 
以下內(nèi)容為程序代碼: 
public class httpapplication : ... 
{ 
private httpcontext _context; 
private httpsessionstate _session; 
public httpsessionstate session 
{ 
get 
{ 
httpsessionstate state = null; 
if (this._session != null) 
{ 
state = this._session; 
} 
else if (this._context != null) 
{ 
state = this._context.session; 
} 
if (state == null) 
{ 
throw new httpexception(httpruntime.formatresourcestring("session_not_available"[img]/images/wink.gif[/img]); 
} 
return state; 
} 
} 
} 
而在 asp.net 頁面中獲取會話的方法也是類似,都是通過 httpcontext 來完成的。 
以下內(nèi)容為程序代碼: 
public class page : ... 
{ 
private httpsessionstate _session; 
private bool _sessionretrieved; 
public virtual httpsessionstate session 
{ 
get 
{ 
if (!this._sessionretrieved) 
{ 
this._sessionretrieved = true; 
try 
{ 
this._session = this.context.session; 
} 
catch (exception) 
{ 
} 
} 
if (this._session == null) 
{ 
throw new httpexception(httpruntime.formatresourcestring("session_not_enabled"[img]/images/wink.gif[/img]); 
} 
return this._session; 
} 
} 
} 
在 httpcontext 中,實際上是通過一個哈希表保存諸如會話對象之類信息的 
以下內(nèi)容為程序代碼: 
public sealed class httpcontext : ... 
{ 
private hashtable _items; 
public idictionary items 
{ 
get 
{ 
if (this._items == null) 
{ 
this._items = new hashtable(); 
} 
return this._items; 
} 
} 
public httpsessionstate session 
{ 
get 
{ 
return ((httpsessionstate) this.items["aspsession"]); 
} 
} 
} 
而 httpcontext.session 所訪問的又是哪兒來的呢?這就又需要回到我們前面提及的 httpapplication.initmodules 函數(shù)。 
在 .net 安裝目錄 config 子目錄下的 machine.config 定義了全局性的配置信息,而 httpapplication 就是使用其中 system.web 一節(jié)的配置信息進(jìn)行初始化的。 
以下內(nèi)容為程序代碼: 
<system.web> 
<httpmodules> 
<add name="outputcache" type="system.web.caching.outputcachemodule" /> 
<add name="session" type="system.web.sessionstate.sessionstatemodule" /> 
<add name="windowsauthentication" type="system.web.security.windowsauthenticationmodule" /> 
<add name="formsauthentication" type="system.web.security.formsauthenticationmodule" /> 
<add name="passportauthentication" type="system.web.security.passportauthenticationmodule" /> 
<add name="urlauthorization" type="system.web.security.urlauthorizationmodule" /> 
<add name="fileauthorization" type="system.web.security.fileauthorizationmodule" /> 
<add name="errorhandlermodule" type="system.web.mobile.errorhandlermodule, system.web.mobile, version=1.0.5000.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a" /> 
</httpmodules> 
</system.web> 
httpmodules 節(jié)點指定了 httpapplication 需要初始化的模塊列表,而在前面提到的 httpapplication.initmodules 函數(shù)正式根據(jù)此列表進(jìn)行初始化的 
以下內(nèi)容為程序代碼: 
private void httpapplication.initmodules() 
{ 
httpmodulesconfiguration cfgmodules = ((httpmodulesconfiguration) httpcontext.getappconfig("system.web/httpmodules"[img]/images/wink.gif[/img]); 
if (cfgmodules == null) 
{ 
throw new httpexception(httpruntime.formatresourcestring("missing_modules_config"[img]/images/wink.gif[/img]); 
} 
_modulecollection = cfgmodules.createmodules(); 
for(int i = 0; i < _modulecollection.count; i++) 
{ 
_modulecollection[i].init(this); 
} 
globalizationconfig cfgglobal = ((globalizationconfig) httpcontext.getappconfig("system.web/globalization"[img]/images/wink.gif[/img]); 
if (cfgglobal != null) 
{ 
_applevelculture = cfgglobal.culture; 
_appleveluiculture = cfgglobal.uiculture; 
} 
} 
session 節(jié)點對于的 system.web.sessionstate.sessionstatemodule 對象將被 httpmodulesconfiguration.createmodules 方法構(gòu)造,并調(diào)用其 init 函數(shù)初始化。sessionstatemodule 類實際上就是負(fù)責(zé)管理并創(chuàng)建會話,用戶完全可以自行創(chuàng)建一個實現(xiàn) ihttpmodule 接口的類,實現(xiàn)會話的控制,如實現(xiàn)支持集群的狀態(tài)同步等等。 
sessionstatemodule.init 方法主要負(fù)責(zé) machine.config 文件中的 sessionstate 配置,調(diào)用 sessionstatemodule.initmodulefromconfig 方法建立相應(yīng)的會話管理器。 
以下內(nèi)容為程序代碼: 
<system.web> 
<sessionstate mode="inproc" 
stateconnectionstring="tcpip=127.0.0.1:42424" 
statenetworktimeout="10" 
sqlconnectionstring="data source=127.0.0.1;integrated security=sspi" 
cookieless="false" 
timeout="20" /> 
</system.web> 
sessionstate 的使用方法請參加 msdn 中相關(guān)介紹和 info: asp.net state management overview。關(guān)于不同 mode 的會話管理的話題我們后面再討論,先繼續(xù)來看會話的建立過程。 
在從 machine.config 文件中讀取配置信息后,initmodulefromconfig 方法會向 httpapplication 實例注冊幾個會話管理事件處理函數(shù),負(fù)責(zé)在應(yīng)用程序合適的情況下維護(hù)會話狀態(tài)。 
以下內(nèi)容為程序代碼: 
private void sessionstatemodule.initmodulefromconfig(httpapplication app, 
sessionstatesectionhandler.config config, bool configinit) 
{ 
// 處理不使用 cookie 的情況 
//... 
app.addonacquirerequeststateasync(new begineventhandler(this.beginacquirestate), 
new endeventhandler(this.endacquirestate)); 
app.releaserequeststate += new eventhandler(this.onreleasestate); 
app.endrequest += new eventhandler(this.onendrequest); 
// 創(chuàng)建會話管理器 
//... 
} 
beginacquirestate 和 endacquirestate 作為一個異步處理器注冊到 httpapplication._acquirerequeststateeventhandlerasync 字段上;onreleasestate 則負(fù)責(zé)在合適的時候清理會話狀態(tài);onendrequest 則是 onreleasestate 的一個包裝,負(fù)責(zé)較為復(fù)雜的請求結(jié)束處理。前面提到的 httpapplication.initinternal 函數(shù),在完成了初始化工作后,會將上述這些事件處理器,加入到一個執(zhí)行隊列中,由應(yīng)用程序在合適的時候,使用流水線機制進(jìn)行調(diào)用,最大化處理效率。有關(guān) asp.net 中流水線事件模型的相關(guān)介紹,請參考 http pipelines 
securely implement request processing, filtering, and content redirection with http pipelines in asp.net 一文中 the pipeline event model 小節(jié),有機會我在寫文章詳細(xì)分析。 
知道了會話建立的調(diào)用流程再來看會話的實現(xiàn)就比較簡單了,sessionstatemodule.beginacquirestate 被 httpapplication 實例在合適的時候調(diào)用,處理各種會話的復(fù)雜情況后,使用 sessionstatemodule.completeacquirestate 函數(shù)完成實際的會話建立工作,并將封裝會話的 httpsessionstate 對象以 "aspsession" 為 key 加入到 httpcontext 的哈希表中,也就是前面提到的 httpcontext.context 的由來。而 sessionstatemodule.onreleasestate 則從 httpcontext 中刪除 "aspsession" 為 key 的 httpsessionstate 對象,并對會話管理器進(jìn)行同步工作。 
至此,asp.net 中的會話建立流程大概就分析完畢了,下一小節(jié)將進(jìn)一步展開分析多種不同會話管理器的實現(xiàn)原理與應(yīng)用。