Session淺析

為什么會出現(xiàn)Session:

用瀏覽器打開一個網(wǎng)頁,用到的是HTTP協(xié)議。它是無狀態(tài)的,也就是說這一次請求和上一次請求是沒有任何關系的,互不認識的,沒有關聯(lián)的。

這種無狀態(tài)的的好處是快速。但是會帶來一個問題就是,我希望幾個請求的頁面要有關聯(lián),比如:我在xxx/login.php里面登陸了,我在xxx/index.php 也希望是登陸狀態(tài),但是,這是2個不同的頁面,也就是2個不同的HTTP請求,由于HTTP請求是無狀態(tài)的,也就是無關聯(lián)的,所以無法單純的在index.php中讀取到它在login.php中已經(jīng)登陸了!

正是這種訴求,這個時候,一個新的客戶端存儲數(shù)據(jù)方式出現(xiàn)了:cookie. cookie是把少量的信息存儲在用戶自己的電腦上,它在一個域名下是一個全局的,只要設置它的存儲路徑在域名xxx下 ,那么當用戶用瀏覽器訪問時,php就可以從這個域名的任意頁面讀取cookie中的信息。所以就很好的解決了我在xxx/login.php頁面登陸了,我也可以在xxx/index.php獲取到這個登陸信息了。

雖然這種方案很不錯,也很快速方便,但是由于cookie 是存在用戶端,而且它本身存儲的尺寸大小也有限,最關鍵是用戶可以是可見的,并可以隨意的修改,很不安全。那如何又要安全,又可以方便的全局讀取信息呢?于是,這個時候,一種新的存儲會話機制:session誕生了。

session就是在一次會話中解決2次HTTP的請求的關聯(lián),讓它們產(chǎn)生聯(lián)系,讓2兩個頁面都能讀取到找個這個全局的session信息。session信息存在于服務器端,所以也就很好的解決了安全問題。

總結:
cookie的作用就是為了解決HTTP協(xié)議無狀態(tài)的缺陷所作出的努力。
session機制則是又一種在客戶端與服務器之間保持狀態(tài)的解決方案。

例子:
用幾個例子來描述一下cookie和session機制之間的區(qū)別與聯(lián)系。筆者曾經(jīng)常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數(shù)量。想象一下其實也無外乎下面的幾種方案:

  1. 該店的店員很厲害,能記住每位顧客的消費數(shù)量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協(xié)議本身支持狀態(tài)。
  2. 發(fā)給顧客一張卡片,上面記錄著消費的數(shù)量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯(lián)系起來。這種做法就是在客戶端保持狀態(tài)。
  3. 發(fā)給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務器端保持狀態(tài)。
Cookie機制:

cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發(fā);“會員卡”的內(nèi)容;以及客戶如何使用“會員卡”。

正統(tǒng)的cookie分發(fā)是通過擴展HTTP協(xié)議來實現(xiàn)的,服務器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

而cookie的使用是由瀏覽器按照一定的原則在后臺自動發(fā)送給服務器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發(fā)送給服務器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如果某家分店還發(fā)行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

cookie的內(nèi)容主要包括:名字,值,過期時間,路徑和域。

其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。

路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。路徑與域合在一起就構成了cookie的作用范圍。如果不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內(nèi)存里,當然這種行為并不是規(guī)范規(guī)定的。如果設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。

存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內(nèi)存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經(jīng)打開的窗口的內(nèi)存cookie;對于Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內(nèi)存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經(jīng)常給采用session機制的web應用程序開發(fā)者造成很大的困擾。

Session機制和保存:

session機制是一種服務器端的機制,服務器使用一種類似于散列表的結構(也可能就是使用散列表)來保存信息。

當程序需要為某個客戶端的請求創(chuàng)建一個session的時候,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識 - 稱為session id,如果已包含一個session id則說明以前已經(jīng)為此客戶端創(chuàng)建過session,服務器就按照session id把這個session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session id,則為此客戶端創(chuàng)建一個session并且生成一個與此session相關聯(lián)的session id,session id的值應該是一個既不會重復,又不容易被找到規(guī)律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。

保存這個session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規(guī)則把這個標識發(fā)揮給服務器。一般這個cookie的名字都是類似于SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務器。經(jīng)常被使用的一種技術叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現(xiàn)形式為http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
這兩種方式對于用戶來說是沒有區(qū)別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來。
為了在整個交互過程中始終保持狀態(tài),就必須在每個客戶端可能請求的路徑后面都包含這個session id。

在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保留,程序一般都是在用戶做log off的時候發(fā)個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經(jīng)關閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器后這個session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發(fā)出的HTTP請求頭,把原來的session id發(fā)送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。

恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經(jīng)停止了活動,才會把session刪除以節(jié)省存儲空間。

下面用PHP中session的機制講解,其他語言類似。
第一步是開啟session:

session_start();

這是個無任何返回值的函數(shù),既不會報錯,也不會成功。它的作用是開啟session,并隨機生成一個唯一的32位的session_id,類似于這樣:

4c83638b3b0dbf65583181c2f89168ec

session的全部機制也是基于這個session_id,它用來區(qū)分哪幾次請求是一個人發(fā)出的。為什么要這樣呢?因為HTTP是無狀態(tài)無關聯(lián)的,一個頁面可能會被成百上千人訪問,而且每個人的用戶名是不一樣的,那么服務器如何區(qū)分這次是小王訪問的,那次是小名訪問的呢?所以就有了找個唯一的session_id 來綁定一個用戶。一個用戶在一次會話上就是一個session_id,這樣成千上萬的人訪問,服務器也能區(qū)分到底是誰在訪問了。

每次我們訪問一個頁面,如果有開啟session,也就是有session_start() 時,就會自動生成一個session_id 來標注是這次會話的唯一ID,同時也會自動往cookie里寫入一個名字為PHPSESSID的變量,它的值正是session_id,當這次會話沒結束,再次訪問的時候,服務器會去讀取這個PHPSESSID的cookie是否有值有沒過期,如果能夠讀取到,則繼續(xù)用這個session_id,如果沒有,就會新生成一個session_id,同時生成PHPSESSID這個cookie。由于默認生成的這個PHPSESSID cookie是會話,也就是說關閉瀏覽器就會過期掉,所以,下次重新瀏覽時,會重新生成一個session_id。

那么它是怎么存的呢?

同樣也是用到session_id。session_id是32位的,服務器會用 sess_前綴 + session_id 的形式存在這個臨時目錄下,每一次生成的session_id都會生成一個這樣的文件,用來保存這次會話的session信息。

這個sess文件不會隨著客戶端的PHPSESSID過期,也一起過期掉,它會一直存在,出息GC掃描到它過期或者使用session_destroy()函數(shù)摧毀。

大致總結下

HTTP請求一個頁面后,如果用到開啟session,會去讀cookie中的PHPSESSID是否有,如果沒有,則會新生成一個session_id,先存入cookie中的PHPSESSID中,再生成一個sess_前綴文件。當有寫入$SESSION的時候,就會往sess文件里序列化寫入數(shù)據(jù)。當讀取的session變量的時候,先會讀取cookie中的PHPSESSID,獲得session_id,然后再去找這個sess_sessionid文件,來獲取對應的數(shù)據(jù)。由于默認的PHPSESSID是臨時的會話,在瀏覽器關閉后,會消失,所以,我們重新訪問的時候,會新生成session_id和sess_這個文件。

用redis存儲session

之前說的都是用文件files存儲,現(xiàn)在想用redis,好處有哪些?

  1. 更快的讀取和寫入速度。redis是直接操縱內(nèi)存數(shù)據(jù)的,肯定是要比文件的形式快很多。
  2. 更好的設置好過期時間。文件存儲的sess_sdewfrsf文件其實被刪除掉還是要考運氣的和概率的,很有可能造成sess_文件沒即時刪除,造成存儲磁盤空間過多,和讀取SESSION就變慢了。
  3. 更好的分布式同步。設置redis 的主從同步,可以快速的同步session到各臺web服務器,比文件存儲更加快速。

總的說來,用redis來存儲SESSION速度更快,性能更高。

HttpSession常見問題(僅供參考)

1、session在何時被創(chuàng)建

一個常見的誤解是以為session在有客戶端訪問時就被創(chuàng)建,然而事實是直到某server端程序調(diào)用HttpServletRequest.getSession(true)這樣的語句時才被創(chuàng)建,注意如果JSP沒有顯示的使用 <%@page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。

由于session會消耗內(nèi)存資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。

2、session何時被刪除

綜合前面的討論,session在下列情況下被刪除a.程序調(diào)用HttpSession.invalidate();或b.距離上一次收到客戶端發(fā)送的session id時間間隔超過了session的超時設置;或c.服務器進程被停止(非持久session)

3、如何做到在瀏覽器關閉時刪除session

嚴格的講,做不到這一點??梢宰鲆稽c努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監(jiān)視瀏覽器的關閉動作,然后向服務器發(fā)送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規(guī)手段仍然無能為力。

4、有個HttpSessionListener是怎么回事

你可以創(chuàng)建這樣的listener去監(jiān)控session的創(chuàng)建和銷毀事件,使得在發(fā)生這樣的事件時你可以做一些相應的工作。注意是session的創(chuàng)建和銷毀動作觸發(fā)listener,而不是相反。類似的與HttpSession有關的listener還有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。

5、存放在session中的對象必須是可序列化的嗎

不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久保存或者在必要時server能夠暫時把session交換出內(nèi)存。在Weblogic Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。

6、如何才能正確的應付客戶端禁止cookie的可能性

對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session

參見第三小節(jié)對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂

這個問題與防止表單多次提交是類似的,可以通過設置客戶端的令牌來解決。就是在服務器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經(jīng)被提交過了??梢詤⒖础禞2EE核心模式》關于表示層模式的部分。需要注意的是對于使用javascript window.open打開的窗口,一般不設置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設置。

9、為什么在Weblogic Server中改變session的值后要重新調(diào)用一次session.setValue

做這個動作主要是為了在集群環(huán)境中提示W(wǎng)eblogic Server session中的值發(fā)生了改變,需要向其他服務器進程復制新的session值。

10、為什么session不見了

排除session正常失效的因素之外,服務器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務器在cookie處理上也有可能會出現(xiàn)問題。
出現(xiàn)這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節(jié)討論這個問題。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容