會話狀態Session


一、會話狀態Session

  Session用於服務器端狀態管理,使用Session之后,每個客戶端都可以將實際的數據保存在服務器上,對於每個客戶端的數據,將會生成一個對應的唯一的key(保存在客戶端)。客戶端與服務器端就是通過這個key來確認客戶端的身份,通常這個key為SessionID。

  一般情況下,SessionID以Cookie的形式保存在瀏覽器中,在不使用Cookie的情況下,也可以將這個SessionID嵌入到訪問網頁的URL中。

二、服務器端Session

  在頁面對象或者HttpContext對象中,都有一個名為Session的屬性,在一次會話中,它們引用的都是同一個對象。

  public HttpSessionState Session { get; }

  Session對象是HttpSessionState類的實例。Session是保存在服務器端的,對每個登錄到網站的用戶都有一份,是獨有的,而其他用戶無法共享。

  HttpSessionState來自於HttpModule的SessionStateModule。在每次請求處理過程中,HttpApplication的請求的處理管道中會檢查當前請求的處理程序是否實現了接口IRequiresSessionState,如果實現的話,那么SessionStateModule將為這個請求分配HttpSessionState。同時SessionStateModule還負責SessionID的生成、Cookieless會話管理、從外部狀態提供程序中檢索會話數據以及將數據綁定到請求的調用上下文。

  • 對於一般處理程序,默認情況下沒有實現IRequiresSessionState接口。所以如果想要在一般處理程序中使用Session,可以通過實現IRequiresSessionState接口來解決這個問題,這個接口是一個標記接口,並沒有定義任何內容。
  • 對於頁面處理程序,可以將頁面指令@Page的EnableSessionState屬性設置為true,以允許頁面可以請求會話狀態的寫入權限。這是默認的設置。還可以將EnableSessionState屬性設置為ReadOnly,此時派生的實際頁面類將會實現接口IReadOnlySessionState,在這種情況下,頁面可以擁有會話狀態的只讀權限。

  SessionStateModule模塊從特定狀態提供程序中讀取數據。在程序代碼中實際上訪問的是會話數據在本地內存中的副本,如果其他頁面也視圖同步訪問該會話狀態就可能會導致數據沖突。為了避免這種情況,SessionStateModule模塊實現了一個讀取器/寫入器的鎖定機制,並對狀態值的訪問進行排隊。對會話狀態具有寫入權限的頁面將保留該會話的寫入器鎖定,直到請求終止。

  如果頁面請求設置一個讀取器鎖定,同一會話中同時處理的其他請求將無法更新會話狀態,但是至少可以進行讀取。如果頁面請求為會話狀態設置一個寫入鎖,那么所有其他頁面都被阻止,無論他們是否要讀取或寫入內容。例如,如果同時有兩段程序視圖在同一個Session中寫入內容,一段程序必須等到另一段程序完成后才能寫入。在AJAX程序設計中,必須注意這種情況的發生。

  來看一個非常有趣的示例:Asp.net設置session后變單線程執行

  新建一個MVC項目,添加一個Controller如下:

   public class HomeController : Controller
    {
        public ActionResult Index()
        {
            //Session["User"] = "張三";  //特別注意這行代碼

            return View();
        }

        public ActionResult Test1()
        {
            Thread.Sleep(5000);
            return Content("長任務Test1完成");
        }

        public ActionResult Test2()
        {
            return Content("短任務Test2完成");
        }
    }

  /Home/Index視圖代碼如下:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
    <script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function() {
            $("#btn1").click(function() {
                $.ajax({
                    url: "/Home/Test1",
                    dataType: "text",
                    success: function(response) {
                        $("#div1").html($("#div1").html() + response + "<br/>");
                    }
                })

                $.ajax({
                    url: "/Home/Test2",
                    dataType: "text",
                    success: function(response) {
                    $("#div1").html($("#div1").html() + response + "<br/>");
                    }
                })
            })
        })
    </script>
    
</head>
<body>
    <div id="div1" style="width:200px; height:200px; border:1px solid #000;">
    
    </div>
    <input type="button" id="btn1" value="開始" />
</body>
</html>

  在剛開始的時候,Index的Session[]那行代碼是注釋掉的,輸出如下:

  

  乍眼一看,這很正常jQuery的AJAX默認是異步執行,那個先執行完就哪個先顯示,沒問題。

  下面,啟用那行被注釋掉的Session代碼,輸入如下:

  

  這次點擊按鈕沒有反應,雖然jQuery的AJAX是已經發送出去了,但是Asp.net必須要等到第一個請求執行完畢之后,第二個請求才開始執行。這從google瀏覽器的請求信息可以看到,兩個請求幾乎是同時發出去的,是Asp.net使用了Session導致的問題:

  

  特別說明:只有寫Session時,Asp.net才會阻塞請求,但是只要你訪問過寫Session的頁面,比如是用Session登錄的系統之后的操作(直到Session失效都一直鎖定,當然只是SessionID相同的情況)。都會存在這個問題。光讀Session不會出現這種情況。

  這個問題在開發一些並發的Asp.net功能時需要注意,例如進度條。

  在MVC中有一個辦法可以解決這個問題,在MVC中,可以為本Controller增加以下特性,但是本Controller都不能修改Session了,只能讀取。

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

  對於WebForm來說是在aspx頂部的Page后面加上(僅僅加載那個阻塞頁面):

EnableSessionState="ReadOnly"

三、客戶端的SessionID

  對於會話的每個客戶端來說,需要在瀏覽器中保存一個會話的標識,以便在后繼的請求中區分不同的會話,這個標識我們成為SessionID。

  SessionID是由SessionStateModule創建的,在創建一個新的SessionID之后,SessionStateModule將會觸發Session_Start事件,我們可以在Global.asax中處理這個事件。

        void Session_Start(object sender, EventArgs e)
        {
            // 在新會話啟動時運行的代碼
        }

  如果會話超時或被放棄,下次訪問應用程序時,SessionID並不會發生改變。即使會話狀態過期,會話SessionID也能持續到瀏覽器會話結束。也就是說,只要瀏覽器實例相同,就始終使用同一個會話SessionID表示多個會話。

  Session_End事件標志着會話的結束,並用於執行終止該會話所需的所有清除代碼。但請注意,只有InProc模式支持該事件,也就是說,只有將會話數據存儲在Asp.net輔助進程中時才支持該事件。對於要引發Session_End事件來說,必須首先存在會話狀態,這意味着必須在改會話狀態中存儲一些數據,並且必須至少完成一個請求,才會觸發這個事件。

        void Session_End(object sender, EventArgs e)
        {
            // 在會話結束時運行的代碼。 
            // 注意: 只有在 Web.config 文件中的 sessionstate 模式設置為
            // InProc 時,才會引發 Session_End 事件。如果會話模式設置為 StateServer 
            // 或 SQLServer,則不會引發該事件。
        }

   InProc是最常用的也是Asp.net默認的模式,添加到緩存中的會話狀態被賦予一個滑動過期策略。服務器將會進行一個倒計時,當倒計時到0的時候,意味着會話過期。滑動過期時間表示如果在一定時間內沒有使用,將被刪除。在過期之前,處理任何請求的時候,過期時間都將重新設置為會話的過期時間。

  例如,如果Session的過期時間是20分鍾,那么在過期之前,每次請求都會導致這個事件被重新設置為20分鍾。

  過期的會話數據將自動被刪除。狀態會話模塊也包含一個刪除會話的回調函數。當會話數據被刪除的時候,將自動調用刪除函數,然后刪除函數將引發Session_End事件。但是,如果應用程序沒有通過InProc模式來進行會話管理,將永遠不會引發結束事件。
  SessionID由URL允許的120位字符串組成。默認情況下,SessionID以Cookie的形式發送到客戶端保存起來,如果沒有設置Cookie的Expires過期時間屬性,那么SessionID在關閉瀏覽器的時候就失效了。

  SessionID字符串被發送到瀏覽器,然后通過以下兩種方式之一返回服務器應用程序:

  1. 使用Cookie。
  2. 經過修改的URL。

  配置會話設置使用sessionState元素的Cookieless特性:

<sessionState cookieless="true" />

  cookieless false表示使用Cookie,true表示使用URL。如果使用了URL則路徑如:

  http://localhost:9090/website/(S(qifjgkvmcnfu8j93ks74ieu7))/SessionID.aspx

 

四、Session的過期問題

  關於Asp.net會話管理,重要的一點是,僅當將第一個項目添加到內存詞典中時,會話狀態對象的生命周期才開始。如:僅在執行如下代碼片段后,才可以認為Asp.net會話開始。

Session["XXX"] = "XXX";

  Session詞典通常包含Object類型,要向后讀取數據,需要將返回的值轉換為更具體的類型。

string data = Session["XXX"].ToString();

  當將數據保存到Session中時,值會加載到HttpSessionState類包含的特制的詞典類中。完成當前處理的請求時,會將詞典的內容加載到狀態提供程序中。
如果會話超時或被放棄,下次訪問無狀態應用程序時,其會話ID不會變,即使會話狀態過期,SessionID也能持續到瀏覽器會話結束。也就是說,只要瀏覽器實例相同,就始終使用同一個會話ID表示多個會話。

  Session_OnEnd事件標志着會話的結束,並用於執行終止該會話所需的所有清除代碼。但要注意,只有InProc模式支持該事件,也就是說,只有將會話數據存儲在Asp.net輔助進程中時才支持該事件。對於要引發的Session_OnEnd事件來說,必須首先存在會話狀態,這意味着必須在該會話狀態中存儲一些數據,並且必須至少完成一個請求。

五、sessionState配置節

  sessionState配置節主要用於配置Session方面的信息,部分節點如下:

配置節點 說明
mode

mode 取值如下,默認為 InProc 。

Custom :會話狀態正在使用自定義數據存儲來存儲會話狀態信息。

InProc :會話狀態正在處理 ASP.NET 輔助進程。

Off :會話狀態被禁用。

SQLServer :會話狀態正在使用進程外 SQL Server 數據庫存儲狀態信息。

StateServer :  會話狀態將使用進程外 ASP.NET 狀態服務來存儲狀態信息。

cookieName

指定存儲會話標識符的 Cookie 的名稱(就是客戶端的Key),默認值為 "ASP.NET_SessionId"。

cookieless

當網站用到AJAX時,該屬性僅能夠使用UseCookies。

AutoDetect :Asp.net自動判斷,如果瀏覽器支持Cookie,則使用 Cookie 來傳輸;否則,使用Uri傳輸。 如果瀏覽器支持Cookie,但禁用了Cookie,則仍然使用 Cookie。
UseCookies :無論瀏覽器或設備是否支持 Cookie,都使用 Cookie 來保留用戶數據。
UseDeviceProfile :ASP.NET 根據 HttpBrowserCapabilities自動判斷。 如果 HttpBrowserCapabilities 設置指示瀏覽器或設備支持 Cookie,將使用 Cookie;否則,將在查詢字符串中使用一個標識符。
UseUri :無論瀏覽器或設備是否支持Cookie,都使用UseUri來傳輸SessionID

customProvider

自定義會話狀態提供程序的名稱

timeout

指定一個會話多長時間空閑會失效。 對於進程內和狀態服務器模式,timeout 特性不能設置為大於 525,600 分鍾(1 年)的值。

compressionEnabled

進程外的Session是否先壓縮后傳輸。如是否壓縮后再發送到SQLServer

  http://msdn.microsoft.com/zh-cn/library/h6bb9cz9.aspx

  StateServer的Session Mode模式的StateServer會將Session數據存在於aspnet_state.exe里面。與w3wp.exe/aspnet_wp.exe進程相互獨立。如果存放於InProc,則是存放於Asp.net進程內。

六、HttpSessionState類

屬性 說明
CodePage 獲取或設置當前會話的字符集標識符
Contents 獲取對當前會話狀態對象的引用
CookieMode 獲取一個值,該值指示是否為無 Cookie 會話配置應用程序
Count 獲取會話狀態集合中的項數
IsCookieless 獲取一個值,該值指示會話 ID 是嵌入在 URL 中還是存儲在 HTTP Cookie 中。 如果會話嵌入在 URL 中,則為 true;否則,為 false
IsNewSession 獲取一個值,該值指示會話是否是與當前請求一起創建的。  如果會話是與當前請求一起創建的,則為 true;否則,為 false
IsReadOnly 獲取一個值,該值指示會話是否為只讀
IsSynchronized 獲取一個值,該值指示對會話狀態值的集合的訪問是否是同步(線程安全)的
Item 獲取或設置個別會話值Session[]
Keys 獲取存儲在會話狀態集合中所有值的鍵的集合
LCID 獲取或設置當前會話的區域設置標識符 (LCID)
Mode 獲取當前會話狀態模式
SessionID 獲取會話的唯一標識符
StaticObjects 獲取由 ASP.NET 應用程序文件 Global.asax 中的 <object Runat="Server" Scope="Session"/> 標記聲明的對象的集合
SyncRoot 獲取一個對象,該對象可用於同步對會話狀態值的集合的訪問
Timeout  獲取並設置在會話狀態提供程序終止會話之前各請求之間所允許的時間(以分鍾為單位)

方法:

方法 說明
Abandon 取消當前會話
Add 向會話狀態集合添加一個新項
Clear 從會話狀態集合中移除所有的鍵和值
CopyTo 將會話狀態值的集合復制到一維數組中(從數組的指定索引處開始)
GetEnumerator 返回一個枚舉數,可用來讀取當前會話中所有會話狀態的變量名稱
Remove  刪除會話狀態集合中的項
RemoveAll 從會話狀態集合中移除所有的鍵和值
RemoveAt 刪除會話狀態集合中指定索引處的項

  一旦調用 HttpSessionState.Abandon方法,當前會話不再有效,同時會啟動新的會話。Abandon 使 SessionStateModule.End 事件被引發。發送下一次請求后將引發新的 SessionStateModule.Start 事件。如果要用Session.Abandon();最好放在一個獨立的頁面。

 代碼示例:

        protected void Page_Load(object sender, EventArgs e)
        {
            Session.Add("username","admin");
            Session.Add("password","123456");

            Response.Write(Session.Count);                      //輸出2
            Response.Write(Session.IsCookieless);               //輸出 False  表示是嵌入在cookie里
            Response.Write(Session.CodePage);                   //輸出 65001
            Response.Write(Session.Contents["username"]);       //輸出 username  
            Response.Write("<br/>");
            Response.Write(Session.CookieMode);                 //UserCookies
            Response.Write(Session.IsNewSession);               //True
            Response.Write(Session.IsReadOnly);                 //False
            Response.Write(Session.IsSynchronized);             //False

            foreach (string str in Session.Keys)
            {
                Response.Write(str + ":" + Session[str]);       //username:admin password:123456
            }

            Response.Write("<br/>");
            Response.Write(Session.LCID);                       //2052
            Response.Write(Session.Mode);                       //InProc
            Response.Write(Session.SessionID);                  //udtst2kwyltquymd40alduyw

            Response.Write("<br/>");
            Response.Write(Session.StaticObjects.Count);        //0
            Response.Write(Session.SyncRoot);                   //             
            Response.Write(Session.Timeout);                    //20

            string[] strArr = new string[Session.Count];
            Session.CopyTo(strArr,0);
            foreach (string str in strArr)
            {
                Response.Write(str);    //輸出 username password  輸出由key組成的字符串數組
            }

            Session.Remove("password");
            Response.Write(Session.Count);  //輸出1    可以看到少了一個
            Session.RemoveAt(0);            //按索引號刪除一個Session對象
            Response.Write(Session.Count);  //輸出0    可以看到又少了一個
            Session.Add("one","劉備");
            Session.Abandon();
            Response.Write(Session["one"]);
            Response.Write("<a href='/new.aspx'>新頁面</a>");      //由於調用了ababdon()方法,因此點擊此頁面過去,將獲取不到Session["one"]
        }

  new.aspx后台代碼:

        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write(Session["one"]);  //雖然輸出cookie中的值,但是不會輸出任何值
        }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM