本文介紹了PHP會(huì)話控制,主要闡述以下幾點(diǎn)內(nèi)容:
• 會(huì)話控制的產(chǎn)生背景/概念
• cookie的維護(hù)與生命周期(有效時(shí)間)
• session的維護(hù)與生命周期(回收機(jī)制)
• cookie與session之間的區(qū)別與聯(lián)系
• 問(wèn)題1:禁用cookie后session為什么會(huì)失效?
• 問(wèn)題2:IE瀏覽器下丟失session,每次刷新頁(yè)面,都會(huì)生成新的sessionID(Firefox瀏覽器正常)
• session、cookie簡(jiǎn)單實(shí)例
理解會(huì)話控制的概念
理解一個(gè)概念就需要理解他的背景及產(chǎn)生的原因,這里引入WEB環(huán)境及其HTTP協(xié)議。會(huì)話控制產(chǎn)生的背景:
閱讀過(guò)HTTP協(xié)議相關(guān)資料的同學(xué)都會(huì)知道HTTP協(xié)議是WEB服務(wù)器與客戶端(瀏覽器)相互通信的協(xié)議,它是一種無(wú)狀態(tài)協(xié)議,所謂無(wú)狀態(tài),指的是不會(huì)維護(hù)http請(qǐng)求數(shù)據(jù),http請(qǐng)求是獨(dú)立的,不持久的。也就是說(shuō)HTTP協(xié)議沒有一個(gè)內(nèi)建的機(jī)制來(lái)維護(hù)兩個(gè)事務(wù)之間的狀態(tài)或者說(shuō)是關(guān)系吧。當(dāng)一個(gè)用戶在請(qǐng)求一個(gè)頁(yè)面后再去請(qǐng)求另外一個(gè)頁(yè)面時(shí),HTTP將無(wú)法告訴我們這兩個(gè)請(qǐng)求是否來(lái)自同一個(gè)用戶。
由此我們就會(huì)覺得很奇怪了,平時(shí)我們?cè)谡搲涮踊螂娚叹W(wǎng)站購(gòu)物時(shí),只要我們?cè)谶@個(gè)站點(diǎn)內(nèi),不論我們?cè)趺刺D(zhuǎn),從一個(gè)頁(yè)面跑到另一個(gè)頁(yè)面,網(wǎng)站總會(huì)記得我是誰(shuí),比如告訴你購(gòu)買了哪些東西。這是怎么做到的呢,估計(jì)大家猜到了,這就是運(yùn)用了HTTP會(huì)話控制。在網(wǎng)站中跟蹤一個(gè)變量,通過(guò)對(duì)變量的跟蹤,使多個(gè)請(qǐng)求事物之間建立聯(lián)系,根據(jù)授權(quán)和用戶身份顯示不同的內(nèi)容、不同頁(yè)面。
PHP Session會(huì)話控制:
PHP的session會(huì)話是通過(guò)唯一的會(huì)話ID來(lái)驅(qū)動(dòng)的,會(huì)話ID是一個(gè)加密的隨機(jī)數(shù)字,由PHP生成,在會(huì)話的生命周期中都會(huì)保存在客戶端。我們知道客戶端(也就是瀏覽器)保存數(shù)據(jù)的地方只有cookie,所以PHP的會(huì)話ID一般保存在用戶機(jī)器的cookie中。了解cookie后我們知道,瀏覽器是可以禁用cookie的,這樣會(huì)話就會(huì)失效。所以PHP會(huì)話控制還有一種模式,就是在URL中傳遞會(huì)話ID。如果在瀏覽網(wǎng)站時(shí)我們稍加留心的話,有些URL中有一串看起來(lái)像隨機(jī)數(shù)字的字符串,那么其實(shí)很有可能就是URL形式的會(huì)話控制。
講到這里,有些人可能會(huì)有疑問(wèn)了,客戶端只是保存一個(gè)會(huì)話ID,那么會(huì)話控制中保存的會(huì)話變量比如你購(gòu)物時(shí)買的物品列表等,它們是存放在哪個(gè)地方的呢?很顯然,會(huì)話變量是在服務(wù)器端使用的,那么這些會(huì)話變量必定存放在服務(wù)器端。默認(rèn)情況下,會(huì)話變量保存在服務(wù)器的普通文件中(也可以自己配置使用數(shù)據(jù)庫(kù)來(lái)保存,可以Google一下),會(huì)話ID的作用就像是一把鑰匙,在服務(wù)器端保存會(huì)話的文件中找到該會(huì)話ID對(duì)應(yīng)的會(huì)話變量,比如購(gòu)買物品的列表。
那么會(huì)話控制的整個(gè)過(guò)程可能就像這個(gè)樣子,用戶登錄或者第一次瀏覽某個(gè)站點(diǎn)的頁(yè)面時(shí),該站點(diǎn)會(huì)生成一個(gè)PHP的會(huì)話ID并通過(guò)cookie發(fā)送到客戶端(瀏覽器)。當(dāng)用戶點(diǎn)擊該站點(diǎn)的另一個(gè)頁(yè)面時(shí),瀏覽器開始連接這個(gè)URL。在連接之前,瀏覽器會(huì)先搜索本地保存的cookie,如果在cookie中有任何與正在連接的URL相關(guān)的cookie,就將它提交到服務(wù)器。而剛好在登陸或第一次連接時(shí),已經(jīng)產(chǎn)生了一個(gè)與該網(wǎng)站URL相關(guān)的cookie(保存的會(huì)話ID),所以當(dāng)用戶再次連接這個(gè)站點(diǎn)時(shí),站點(diǎn)就可以通過(guò)這個(gè)會(huì)話ID識(shí)別出用戶,從服務(wù)器的會(huì)話文件中取出與這個(gè)會(huì)話ID相關(guān)的會(huì)話變量,從而保持事務(wù)之間的連續(xù)。
接下來(lái)我們了解下兩個(gè)重要的概念:cookie和session
關(guān)于cookie的維護(hù)與生命周期
cookie是在服務(wù)器端被創(chuàng)建并寫回到客戶端瀏覽器,瀏覽器接到響應(yīng)頭中關(guān)于寫cookie的指令則在本地臨時(shí)文件夾中。
創(chuàng)建了一個(gè)cookie文件,其中保存了你的cookie內(nèi)容,cookie內(nèi)容的存儲(chǔ)是鍵值對(duì)的方式,鍵和值都只能是字符串。例如:
文件:Cookie:administrator@localhost/
內(nèi)容格式:voteID100101localhost/15361167667230343893360385046430343691*
cookie的創(chuàng)建:
代碼如下:
setcookie()函數(shù)設(shè)置cookie,函數(shù)原型如下
setcookie(name, value, expire, path, domain);
注釋:cookie標(biāo)題頭必須在發(fā)送其他標(biāo)題頭之前發(fā)送,否則就無(wú)效(這是cookie的限制,而不是PHP的限制)。在發(fā)送 cookie 時(shí),cookie 的值會(huì)自動(dòng)進(jìn)行 URL 編碼,在取回時(shí)進(jìn)行自動(dòng)解碼(為防止 URL 編碼,請(qǐng)使用 setrawcookie() 取而代之)。
cookie的維護(hù):
cooke有四個(gè)標(biāo)識(shí)符:cookie的name,domain,path,secure標(biāo)記。要想在將來(lái)改變這個(gè)cookie的值,需要發(fā)送另一個(gè)具有相同cookie name,domain,path的Set-Cookie消息頭,這將以一個(gè)新
的值來(lái)覆蓋原來(lái)cookie的值。然而,如果僅僅只是改變這些選項(xiàng)的某一個(gè)也會(huì)創(chuàng)建一個(gè)完全不同的cookie,如只是更改了name值。
cookie失效時(shí)間:
可以設(shè)置過(guò)期時(shí)間,如果不設(shè)置則是會(huì)話級(jí)別的,即關(guān)閉瀏覽器就會(huì)消失。當(dāng)cookie創(chuàng)建時(shí)包含了失效日期,這個(gè)失效日期則關(guān)聯(lián)了以name-domain-path-secure為標(biāo)識(shí)的cookie。要改變一個(gè)cookie的失效日期,你必須指定同樣的組合。當(dāng)改變一個(gè)cookie的值時(shí),你不必每次都設(shè)置失效日期,因?yàn)樗皇莄ookie標(biāo)識(shí)信息的組成部分。例如:
代碼如下:
setcookie(vote ,$id+1,time()+3600*24);
setcookie(vote,$id);
在cookie上的失效日期并沒有改變,因?yàn)閏ookie的標(biāo)識(shí)符是相同的。實(shí)際上,只有你手工的改變cookie的失效日期,否則其失效日期不會(huì)改變。這意味著在同一個(gè)會(huì)話中,一個(gè)會(huì)話cookie可以變成一個(gè)持久化cookie(一個(gè)可以在多個(gè)會(huì)話中存在的),反之則不可。為了要將一個(gè)持久化cookie變?yōu)橐粋€(gè)會(huì)話cookie,你必須刪除這個(gè)持久化cookie,這只要設(shè)置它的失效日期為過(guò)去某個(gè)時(shí)間之后再創(chuàng)建一個(gè)同名的會(huì)話cookie就可以實(shí)現(xiàn)。
需要記得的是失效日期是以瀏覽器運(yùn)行的電腦上的系統(tǒng)時(shí)間為基準(zhǔn)進(jìn)行核實(shí)的。沒有任何辦法來(lái)來(lái)驗(yàn)證這個(gè)系統(tǒng)時(shí)間是否和服務(wù)器的時(shí)間同步,所以當(dāng)服務(wù)器時(shí)間和瀏覽器所處系統(tǒng)時(shí)間存在差異時(shí)這樣的設(shè)置會(huì)出現(xiàn)錯(cuò)誤。
cookie自動(dòng)刪除:
cookie會(huì)被瀏覽器自動(dòng)刪除,通常存在以下幾種原因:
會(huì)話cooke(Session cookie)在會(huì)話結(jié)束時(shí)(瀏覽器關(guān)閉)會(huì)被刪除
持久化cookie(Persistent cookie)在到達(dá)失效日期時(shí)會(huì)被刪除,如:
代碼如下:
setcookie("vote", "", time()-3600);
如果瀏覽器中的cookie限制到達(dá),那么cookies會(huì)被刪除以為新建cookies創(chuàng)建空間。
關(guān)于session的維護(hù)與生命周期
Session是由應(yīng)用服務(wù)器維持的一個(gè)服務(wù)器端的存儲(chǔ)空間,用戶在連接服務(wù)器時(shí),會(huì)由服務(wù)器創(chuàng)建生成一個(gè)唯一的sessionID,用該sessionID為標(biāo)識(shí)符來(lái)存取服務(wù)器端的Session存儲(chǔ)空間,在會(huì)話期間,分配給客戶端的唯一sessionID,用來(lái)標(biāo)識(shí)當(dāng)前用戶,與其他用戶進(jìn)行區(qū)分。通過(guò)SessionID接受每一次訪問(wèn)的請(qǐng)求,從而識(shí)別當(dāng)前用戶,跟蹤和保持用戶的具體資料,以及session變量,可在session中存儲(chǔ)數(shù)字或文字資料.比如session_name.這些信息都保存在服務(wù)器端。當(dāng)然,sessionID也可以作為會(huì)話信息保存到數(shù)據(jù)庫(kù)中,進(jìn)行session持久化。這樣可以跟蹤用戶的登陸次數(shù)、在線與否、在線時(shí)間等從而維護(hù)HTTP無(wú)狀態(tài)事物之間的關(guān)系。session的內(nèi)容存儲(chǔ)是鍵值對(duì)的列表,鍵是字符串類型,session的存儲(chǔ)更方便,值可以是對(duì)象。
在session會(huì)話期間,session會(huì)分別保存在客戶端和服務(wù)器端兩個(gè)文件,客戶端可以是cookie方式保存的sessionID(默認(rèn)的保存方式)或通過(guò)url字符串形式傳遞。服務(wù)器端一般以文本的形式保存在指定的session目錄中。在服務(wù)器端我們可以通過(guò)session.use_cookies來(lái)控制客戶端使用哪一種保存方式。如果定義為cookie保存方式,我們可以通過(guò)session.cookie_lifetime(默認(rèn)值0,閉瀏覽器就清除)來(lái)控制被保存在client上的cookie的有效期。而如果客戶端用cookie方式保存的sessionID,則使用“臨時(shí)”的cookie保存(cookie的名稱為PHPSESSID,通過(guò)Firebug你可以了解到詳細(xì)的信息,該名稱你可以通過(guò)php.ini session.name進(jìn)行更改),用戶提交頁(yè)面時(shí),會(huì)將這一SessionID提交到服務(wù)器端,來(lái)存取session數(shù)據(jù)。這一過(guò)程,是不用開發(fā)人員干預(yù)的。
Session的創(chuàng)建:
代碼如下:
session_start() //開始一個(gè)會(huì)話及返回已經(jīng)存在會(huì)話
功能:初始化Session,也標(biāo)識(shí)著session生命周期的開始。要使用session,必須初始化一個(gè)session環(huán)境,有點(diǎn)類似于OOP概念中調(diào)用構(gòu)造函數(shù)構(gòu)創(chuàng)建對(duì)象實(shí)例一樣。session初始化操作,聲明一個(gè)全局?jǐn)?shù)組$_SESSION,映射寄存在內(nèi)存的session數(shù)據(jù)。如果session文件已經(jīng)存在,并且保存有session數(shù)據(jù),session_start()則會(huì)讀取session數(shù)據(jù),填入$_SESSION中,開始一個(gè)新的session生命周期。
說(shuō)明:這個(gè)函數(shù)沒有參數(shù),且返回值為true,如果使用基于cookie的sessin,那么在session_satrt()之前不能有任何的輸出,包括空白
如果在php.ini中session.auto_start=1開啟,則在每個(gè)頁(yè)面執(zhí)行session_start(),不需要手工設(shè)置,該選項(xiàng)默認(rèn)為關(guān)閉狀態(tài),開啟后不能將對(duì)象放入session中。
Session ID:
用戶session唯一標(biāo)識(shí)符,隨機(jī)生成的一串字符串,具有唯一性,隨機(jī)性。主要用于區(qū)分其它用戶的session數(shù)據(jù)。用戶第一次訪問(wèn)web頁(yè)面的時(shí)候,php的session初始化函數(shù)調(diào)用會(huì)分配給當(dāng)前來(lái)訪用戶一個(gè)唯一的ID,也稱之為session_id。
獲得session_id():
代碼如下:
echo $_COOKIE['PHPSESSID'].'<br/>';
echo $_COOKIE[session_name()].'<br/>';
echo session_id().'<br/>';
session數(shù)據(jù):
我們把需要通過(guò)session保存的用戶狀態(tài)信息,稱為用戶session數(shù)據(jù),也稱為session data。一般是在當(dāng)前session生命周期內(nèi),相應(yīng)的$_SESSION數(shù)據(jù)。一旦調(diào)用了session_start()初始化session,就意味著開始了一個(gè)session生命周期。也就是宣布了,可以使用相關(guān)函數(shù)操作$_SESSION來(lái)管理session數(shù)據(jù)。這個(gè)session生命周期產(chǎn)生的數(shù)據(jù)并沒有實(shí)時(shí)地寫入session文件,而是通過(guò)$_SESSION變量寄存在內(nèi)存中。$_SESSION是一個(gè)全局變量,類型是Array,映射了session生命周期的session數(shù)據(jù),寄存在內(nèi)存中。在session初始化的時(shí)候,從session文件中讀取數(shù)據(jù),填入該變量中。在session(生命周期)結(jié)束時(shí),將$_SESSION數(shù)據(jù)寫回session文件。
注冊(cè)一個(gè)會(huì)話變量:
從PHP4.1以后,會(huì)話變量保存在超級(jí)全局?jǐn)?shù)組$_SESSION中。要?jiǎng)?chuàng)建一會(huì)話變量,只需要在數(shù)組中設(shè)置一個(gè)元素,如:
代碼如下:
$_SESSION['domain'] = blog.jb51.net;
$_SESSION['poll']=$_SESSION[poll] + 1;
使用一個(gè)會(huì)話變量:
代碼如下:
echo $_SESSION['blogdomain']; //打印出blog.jb51.net,使用會(huì)話前必須先使用session_start()函數(shù)啟動(dòng)一個(gè)會(huì)話
注銷Session變量/銷毀會(huì)話:
代碼如下:
unset($_SESSION); //銷毀單個(gè)會(huì)話變量
如:unset($_SESSION['blogdomain']);
#unset($_SESSION)這個(gè)函數(shù)會(huì)將全局變量$_SESSION銷毀,而且還沒有可行的辦法將其恢復(fù)。用戶也不再可以注冊(cè)$_SESSION變量,所以此函數(shù)千萬(wàn)不可使用。
session_unset(); //多項(xiàng)釋放。將所有登陸在session文件里的變量釋放出來(lái)
#在session生命周期,從當(dāng)前session中注銷全部session數(shù)據(jù),讓$_SESSION成為一個(gè)空數(shù)組。它與unset($_SESSION)的區(qū)別在于:unset直接刪除$_SESSION變量,釋放內(nèi)存資源;另一個(gè)區(qū)別在于,session_unset()僅在session生命周期能夠操作$_SESSION數(shù)組,而unset()則在整個(gè)頁(yè)面(page)生命周期都能操作$_SESSION數(shù)組。session_unset()同樣不進(jìn)行任何IO操作,只影響$_SESSION數(shù)組。
$_SESSION=array(); //多項(xiàng)釋放,釋放所有登錄在$_SESSION參數(shù)里的變量
session_destroy();
#當(dāng)使用完一個(gè)會(huì)話后,首先應(yīng)該注銷所有的變量,然后再調(diào)用該函數(shù)結(jié)束當(dāng)前的會(huì)話,并清空會(huì)話中的所有資源,刪除服務(wù)器上的session文件.該函數(shù)不會(huì)unset(釋放)和當(dāng)前session相關(guān)的全局變量,也不會(huì)刪除客戶端的session cookie
#如果說(shuō)session_start()初始化一個(gè)session的話,而它則注銷一個(gè)session。意味著session生命周期結(jié)束了。在session生命周期結(jié)整后, session_unset, $_SESSION['domain'] 都將不能操作$_SESSION數(shù)組,而$_SESSION數(shù)組依然可以被unset()等函數(shù)操作。這時(shí),session意味著是未定義的,而$_SESSION依然是一個(gè)全局變量,他們脫離了關(guān)映射關(guān)系。
通過(guò)session_destroy()注銷session,除了結(jié)束session生命周期外,它還會(huì)刪除sesion文件,但不會(huì)影響當(dāng)前$_SESSION變量。即它會(huì)產(chǎn)生一個(gè)IO操作。
備注:
1、php默認(rèn)的session是基于cookie的,如果要?jiǎng)h除cookie的話,必須借助setcookie()函數(shù)
2、session_unset()和unset()函數(shù)區(qū)別:
在session生命周期,session_unset()從當(dāng)前session中注銷全部session數(shù)據(jù),讓$_SESSION成為一個(gè)空數(shù)組。它與unset($_SESSION)的區(qū)別在于:unset直接刪除$_SESSION變量,釋放內(nèi)存資源;另一個(gè)區(qū)別在于,session_unset()僅在session生命周期能夠操作$_SESSION數(shù)組,而unset()則在整個(gè)頁(yè)面(page)生命周期都能操作$_SESSION數(shù)組。session_unset()同樣不進(jìn)行任何IO操作,只影響$_SESSION數(shù)組。
Session生命周期(session lifetime):Session失效時(shí)間與過(guò)期回收機(jī)制
我們把初始化session開始,直到注銷session這段期間,稱為session生命周期
默認(rèn)的,php會(huì)將session保存在php.ini配置中session.save_path設(shè)定的目錄下,文件名為這個(gè)樣子:sess_ves0d7uvdsab9k6sig73mnn592。每一個(gè)文件對(duì)應(yīng)了一個(gè)session(會(huì)話)。session文件格式大致如下:
代碼如下:
poll_200|i:1;poll_100|i:3; //#變量名|類型:長(zhǎng)度:值
設(shè)置SESSION的生命周期:
php session是基于cookie的,所以要設(shè)置session的生命周期,首先要設(shè)置cookie的失效時(shí)間。因?yàn)樵诳蛻舳耍ㄈ鐬g覽器)登錄網(wǎng)站時(shí),SESSION 是否有用,首先找客戶端是否有 COOKIE,通過(guò)COOKIE 中的 SESSION ID 去找服務(wù)器上的文件。
代碼如下:
session_start();
$lifeTime = 24 * 3600; // 保存一天
setcookie(session_name(), session_id(), time() + $lifeTime, "/");
其實(shí)PHP5 Session還提供了一個(gè)函數(shù) session_set_cookie_params(); 來(lái)設(shè)置PHP5 Session的生存期的,該函數(shù)必須在 session_start() 函數(shù)調(diào)用之前調(diào)用:
代碼如下:
$lifeTime = 24 * 3600; // 保存一天
session_set_cookie_params($lifeTime);
session_start();
在服務(wù)器端,php如何判斷session文件是否過(guò)期?
代碼如下:
session.gc_maxlifetime = 1440 (初始值)
#設(shè)置session存活時(shí)間,單位是秒。每次GC啟動(dòng)后, 會(huì)通過(guò)stat得到session文件最后訪問(wèn)的unix時(shí)間,通過(guò)現(xiàn)在時(shí)間減去文件最后訪問(wèn)時(shí)間之間大于session.gc_maxlifetime,則會(huì)刪除該文件。
如果"最后的修改時(shí)間"到"現(xiàn)在"超過(guò)了session.gc_maxlifetime(默認(rèn)是1440)秒,也就是說(shuō)在這里設(shè)置的時(shí)間內(nèi),該文件沒有被修改過(guò),這個(gè)session文件就被認(rèn)為是過(guò)期了,由于php5的session采用被動(dòng)的回收機(jī)制,過(guò)期的session文件不會(huì)自己消失,而是通過(guò)觸發(fā)“回收”來(lái)處理過(guò)期的session,那么在下一次session回收的時(shí)候,如果這個(gè)文件仍然沒有被更改過(guò),這個(gè)session文件就會(huì)被刪除(session就過(guò)期了)。
session回收何時(shí)發(fā)生?
默認(rèn)情況下,每一次php請(qǐng)求,就會(huì)有1%的概率發(fā)生回收,所以可能簡(jiǎn)單的理解為“每100次php請(qǐng)求就可能有一次回收概率發(fā)生”。這個(gè)概率是通過(guò)以下參數(shù)控制的:
代碼如下:
session.gc_probability = 1 (初始值)
session.gc_divisor = 100 (初始值)
#由這二個(gè)函數(shù)決定了啟用GC的概率,默認(rèn)是1/1000。也就是說(shuō),每一千次用戶請(qǐng)求中有一次會(huì)啟動(dòng)GC回收session。啟動(dòng)GC進(jìn)程不宜過(guò)于頻繁。過(guò)于頻繁訪問(wèn)的網(wǎng)站,并發(fā)量大的網(wǎng)站,可減小PHP GC的啟動(dòng)頻率。PHP GC回收session會(huì)降低php的執(zhí)行效率。
這兩個(gè)合起來(lái)就是啟動(dòng)Gabadge Collection(gc)進(jìn)程管理概率的,在session初使化時(shí)(session_start())。Gabadge Collection啟動(dòng)后跟蹤session信息文件。其啟動(dòng)概率為session.gc_probability/session.gc_divisor。也就是說(shuō)不是每個(gè)session信息文件都有100%的被系統(tǒng)當(dāng)作垃圾來(lái)處理的。如果直接關(guān)閉瀏覽器的話,session信息文件很多情況下都是留在了服務(wù)器上,如果把概率改成了100%,雖然Gabadge Collection百分之百被啟動(dòng)了,但是這會(huì)對(duì)服務(wù)器添加負(fù)荷,也就失去了GC本身的意義了。
補(bǔ)充說(shuō)明:
1、假設(shè)這種情況session.gc_maxlifetime=1440,如果某個(gè)session文件最后修改時(shí)間是1440秒之前,那么在下一次回收(1/100的概率)發(fā)生前,這個(gè)session仍然是有效的;
2、如果你的session使用session.save_path中使用別的地方保存session,session回收機(jī)制有可能不會(huì)自動(dòng)處理過(guò)期session文件。這時(shí)需要定時(shí)手動(dòng)(或者crontab)的刪除過(guò)期的session:cd /path/to/sessions; find -cmin +24 | xargs rm;
3、注意,當(dāng)服務(wù)器端session文件數(shù)量沒有得到有效的回收,逐漸增長(zhǎng)到GB或更大級(jí)別時(shí)可能你的站點(diǎn)在存取session時(shí)就會(huì)越來(lái)越緩慢,多見于站點(diǎn)登入登出會(huì)受到影響;
4、寫日志、周報(bào)、月報(bào)等時(shí)候我們最后提交的關(guān)頭,有時(shí)會(huì)出現(xiàn)”無(wú)效的操作,請(qǐng)登陸后重試”等消息,其原因也不言而喻,可能就是session失效,gc清除那些已經(jīng)“超時(shí)”的session文件。
一些特殊情況:
因?yàn)榛厥諜C(jī)制會(huì)檢查文件的“最后修改時(shí)間”,所以如果某個(gè)會(huì)話是活躍的,但是session的內(nèi)容沒有改變過(guò),那么對(duì)應(yīng)的session文件也就沒有改變過(guò),回收機(jī)制會(huì)認(rèn)為這是一個(gè)長(zhǎng)時(shí)間沒有活躍的session而將其刪除。這是我們不愿看到的,可以通過(guò)增加如下的簡(jiǎn)單代碼解決這個(gè)問(wèn)題:
代碼如下:
<?php
if(!isset($_SESSION['last_access'])||(time()-$_SESSION['last_access'])>120)
$_SESSION['last_access'] = time();
?> //代碼會(huì)每隔120秒,嘗試修改修改一次session
了解cookie與session之間的區(qū)別與聯(lián)系
相同點(diǎn):都可以在解決HTTP無(wú)狀態(tài)的問(wèn)題,使同一個(gè)客戶端在訪問(wèn)網(wǎng)站的多次請(qǐng)求中,可以保存,設(shè)置信息,并且在請(qǐng)求事物之間建立聯(lián)系。
不同點(diǎn):簡(jiǎn)單的說(shuō)cookie的信息保存在客戶端,session的信息保存在服務(wù)器端。
Session采用鍵值對(duì),也就是說(shuō)ID存放客戶端,而值放在服務(wù)器端,是通過(guò)用戶的ID去找服務(wù)器上對(duì)應(yīng)的值,這種方式值放置在服務(wù)器端,有個(gè)時(shí)間限制,時(shí)間到則服務(wù)器自動(dòng)回收/釋放。
Cookies則有兩種方法,一種方法是把值保存在瀏覽器的變量中,當(dāng)瀏覽器關(guān)閉時(shí)結(jié)束,另一種方法是保存在硬盤中,只要時(shí)間不過(guò)期,下次還可使用。
聯(lián)系:當(dāng)客戶端使用基于Cookie方式保存的SessionID時(shí),SessionID一般保存在cookie中。
備注:cookie在相同內(nèi)核的瀏覽器之間是共享的,不同內(nèi)核瀏覽器是不共享的例如火狐和IE(存放位置都不同,當(dāng)然不共享)。不同內(nèi)核瀏覽器不能共享cookie,也會(huì)產(chǎn)生不同sessionid。
問(wèn)題1:禁用cookie后session為什么會(huì)失效?
首先說(shuō)明一點(diǎn):session不一定必須依賴cookie,只是php默認(rèn)客戶端sessionid基于cookie方式保存。
到此,我想你也應(yīng)該了解了php默認(rèn)的session客戶端保存方式是基于cookie的,所以一旦客戶端禁用Cookie,那么session跨頁(yè)將會(huì)失效,不知道這么描述是否合適,通俗的說(shuō)無(wú)狀態(tài)的東西要變的有狀態(tài),只能兩邊都進(jìn)行比對(duì),如果用cookie方式保存的SessionID,客戶端這邊的比對(duì)條件就放到cookie里,所以客戶端禁用cookie,session便也會(huì)隨之失效。php的session客戶端ID一般有兩種保存方式:cookie和url方式。如果是cookie中保存session ID,就可以看到瀏覽器的cookie中有一個(gè)PHPSESID變量(可以通過(guò)firefox查看)。如果是URL傳遞的(建議使用隱藏表單傳遞),就可以看到形如:index.php?PHPSESID=ves0d7uvdsab9k6sig73mnn592的URL。例如:
代碼如下:
demo1.php
<?php
session_start();
$_SESSION['blog']='http://blog.jb51.net';
echo "<a href='demo2.php'>test2</a>";
?>
demo2.php
<?php
session_start();
echo 'session值為'.$_SESSION['blog'];
?>
運(yùn)行上面的代碼,在客戶端cookie正常情況下,我么可以在demo2.php中打印出$_SESSION['blog']的值為:。但是,現(xiàn)在如果你手動(dòng)禁用客戶端的cookie,再運(yùn)行該實(shí)例,可能就得不到結(jié)果了。因?yàn)槟J(rèn)的客戶端sessionid保存方式在跨頁(yè)后,讀取不到前一頁(yè)的sessionid,當(dāng)執(zhí)行session_start();將又會(huì)產(chǎn)生一個(gè)session文件,與之對(duì)應(yīng)產(chǎn)生相應(yīng)的session id,用這個(gè)session id是取不出前面提到的第一個(gè)session文件中的變量的,因?yàn)檫@個(gè)session id不是打開它的“鑰匙”。如果在session_start();之前加代碼session_id($sessionid);將不產(chǎn)生新的session文件,直接讀取與這個(gè)id對(duì)應(yīng)的session文件。簡(jiǎn)單的說(shuō)就是在前一頁(yè)取得session id,然后想辦法傳遞到下一頁(yè),在下一頁(yè)的session_start();代碼之前加代碼session_id(傳過(guò)來(lái)的sessionid); 例如:
代碼如下:
demo.php
<?php
$sid = $_GET['sid'];
if(!empty($sid)){
session_id($sid);
session_start();
}else{
session_start();
$sid = session_id();
}
?>
<form action="demo2.php?sid=<?php echo $sid ?>" method="post">
<input type="text" name="id" value="100" />
<input type="submit" value="提交"/>
</form>
demo2.php
<?php
$sid = $_GET['sid'];
if(!empty($sid)){
session_id($sid);
session_start();
}else{
session_start();
$sid = session_id();
}
$id = $_POST['id'];
$key = 'poll_'.$id;
if($id!=''){
echo $key = 'poll'.$id;
if(!empty($_SESSION[$key])){
$_SESSION[$key]=$_SESSION[$key] + 1;
}else{
$_SESSION[$key]=1;
setcookie($key ,$id+1,time()+3600*24);
}
echo '<script>alert("success");javascript:location.href="demo.php?sid='.$sid.'";</script>';
}else{
echo '<script>alert("failed!ID Null");javascript:history.back(-1);</script>';
}
?>
除此之外,我們還可以將客戶端PHPSESID存放到文件中,如:
代碼如下:
demo.php
session_start();
$_SESSION['blogdomain']= 'http://blog.jb51.net';
$sid=session_id();
$fp=fopen("D:\tmp\websid.txt","w+");
fwrite($fp,$sid);
fclose($fp);
echo '<a href="demo2.php">demo2</a>';
demo2.php
$fp=fopen("D:\tmp\websid.txt","r");
$sid=fread($fp,1024);
fclose($fp);
session_id($sid);
session_start();
print_r($_SESSION);
當(dāng)客戶端禁用cookie,可以通過(guò)以下幾種方式改變session對(duì)客戶端cookie的依賴,使session拋開客戶端cookie:
1、設(shè)置php.ini中的session.use_trans_sid = 1或者編譯時(shí)打開打開了--enable-trans-sid選項(xiàng),讓PHP自動(dòng)跨頁(yè)傳遞session id。當(dāng)session.use_trans_sid為有效時(shí),ession.use_only_cookies一定要設(shè)置為無(wú)效0。
2、手動(dòng)通過(guò)URL傳值、隱藏表單傳遞session id。
3、用文件、數(shù)據(jù)庫(kù)等形式保存session_id,在跨頁(yè)過(guò)程中手動(dòng)調(diào)用。
PHP也提供一個(gè)函數(shù):
代碼如下:
output_add_rewrite_var ( string $name , string $value ) # 變量名 變量值
說(shuō)明:此函數(shù)給URL重寫機(jī)制添加名/值對(duì)。 這種名值對(duì)將被添加到URL(以GET參數(shù)的形式)和表單(以input隱藏域的形式),當(dāng)透明URL重寫用 session.use_trans_sid 開啟時(shí)同樣可以添加到session ID。 要注意,絕對(duì)URL(..)不能被重寫。此函數(shù)的行為由url_rewriter.tags php.ini 參數(shù)控制。
代碼如下:
<?
session_start();
output_add_rewrite_var('PHPSESSID',session_id ());
echo '<a href="demo2.php">demo</a>';
?>
這樣sessionID會(huì)跟在URL后面而且from中會(huì)出現(xiàn)sessionID的hidden值。
改變session客戶端ID保存方式:
session.use_cookies //控制客戶端保存SessionID時(shí)使用哪一種方式,當(dāng)它為“1”時(shí),就說(shuō)明啟動(dòng)了session cookie(初始值為1)
可以使用上面我們提到的函數(shù)來(lái)查詢得到目前的session id:echo $_COOKIE["PHPSESSID"];
但是,如果client的瀏覽器不支持cookie的話,即使session.use_cookies這個(gè)參數(shù)的值等于“1”,用上述的查詢也只會(huì)得到null。
php.ini中兩個(gè)和該選項(xiàng)相關(guān)的配置參數(shù):
代碼如下:
session.use_cookies = 1 //是否使用cookies(默認(rèn)值為1)
session.use_only_cookies=1 //為1時(shí)只使用cookie;為0時(shí)可使用cookie和其它方式,這時(shí)如果客戶端cookie可用,則session還是默認(rèn)用cookie(默認(rèn)值為1)
注意:如果客戶的瀏覽器是支持cookie的,強(qiáng)烈推薦“session.use_only_cookies = 1”,當(dāng)session.use_only_cookies為有效時(shí),即使想通過(guò)URL來(lái)傳遞session id也會(huì)被認(rèn)為無(wú)效,這樣可以減少通過(guò)sessionid被攻擊的可能性。上面兩個(gè)配置,在php代碼頁(yè)面中設(shè)置方式:
代碼如下:
ini_set('session.use_cookies','1');
ini_set('session.use_only_cookies','1');
IE下丟失session,每次刷新頁(yè)面,都會(huì)生成新的sessionID(Firefox瀏覽器都正常)
如果你的服務(wù)器或站點(diǎn)出現(xiàn)這種問(wèn)題,請(qǐng)正確配置session.cookie_path網(wǎng)站域,如果配置錯(cuò)誤可能會(huì)引起以下常見故障:
(1)客戶端的每個(gè)PHPSESSID在服務(wù)器端都會(huì)一對(duì)一的對(duì)應(yīng)生成一個(gè)獨(dú)立的session記錄存儲(chǔ)在服務(wù)器端,故服務(wù)器端session文件冗余將會(huì)增多(GC回收機(jī)制異常時(shí)、站點(diǎn)訪問(wèn)量較大時(shí))
(2)使用session記錄相關(guān)信息的站點(diǎn)可能在除Firefox(Chrome未測(cè)試)之外的瀏覽器下訪問(wèn)出現(xiàn)問(wèn)題,例如:購(gòu)物車無(wú)法記錄選購(gòu)項(xiàng)目、站點(diǎn)登錄失敗等
代碼如下:
session.cookie_path 是指 session 生效的網(wǎng)站域;
session.save_path 是指存儲(chǔ) session 臨時(shí)文件的路徑。
例如:session.cookie_path= / //cookie的有效路徑
補(bǔ)充:如果所有瀏覽器訪問(wèn)刷新產(chǎn)生新sessionID,請(qǐng)檢查客戶端是否禁用了cookie。
session簡(jiǎn)單實(shí)例
使用session防止表單重復(fù)提交:
代碼如下:
<?php
session_start();
$_SESSION["num"] = 0;
if(isset($_POST["action"] && $_POST["action"]=="post")){
if($_SESSION["num"] == 0){
echo "提交成功!";
$_SESSION["num"] = 1;
}else{
echo "請(qǐng)勿重復(fù)提交!";
}
}
?>
使用session方式的登錄驗(yàn)證實(shí)例代碼:
代碼如下:
<?php
session_start();//啟動(dòng)session,必須放在第一句,否則會(huì)出錯(cuò)。
if($_GET['out']){
unset($_SESSION['id']);
unset($_SESSION['pass']);
}
if($_POST['name']&&$_POST['password']){
<span style="font-family: 微軟雅黑;"><span style="font-size: 16px;line-height:2.5em;">//用于設(shè)置session</span></span>
$_SESSION['id']=$_POST['name'];
$_SESSION['pass']=$_POST['password'];
}
if($_SESSION['id']&&$_SESSION['pass']){
echo "登錄成功!
用戶ID:".$_SESSION['id']."<br />用戶密碼:".$_SESSION['pass'];
echo "<br />";
echo "<a href='login.php?out=out'>注銷session</a>";
}
?>
<form action="login.php" method="post">
用戶ID:<input type="text" name="name" />
密碼:<input type="password" name="password" />
<br />
<input type="submit" name="submit">
</form>
使用cookie方式的登錄驗(yàn)證實(shí)例代碼:
代碼如下:
if($_GET['out']){ //用于注銷cookies
setcookie('id',"");
setcookie('pass',"");
echo "<script>location.href='login.php'</script>"; //因?yàn)閏ookies不是及時(shí)生效的,只有你再次刷新時(shí)才生效,所以,注銷后讓頁(yè)面自動(dòng)刷新。
}
if($_POST['name']&&$_POST['password']) //如果變量用戶名和密碼存在時(shí),在下面設(shè)置cookies
{ //用于設(shè)置cookies
setcookie('id',$_POST['name'],time()+3600);
setcookie('pass',$_POST['password'],time()+3600);
echo "<script>location.href='login.php'</script>"; //讓cookies及時(shí)生效
}
if($_COOKIE['id']&&$_COOKIE['pass']){ //cookies設(shè)置成功后,用于顯示cookies
echo "登錄成功!<br />用戶名:".$_COOKIE['id']."
密碼:".$_COOKIE['pass'];
echo "<br />";
echo "<a href='login.php?out=out'>注銷cookies</a>";
}
?>
<form action="" method="post">
用戶ID:<input type="text" name="name" />
密 碼:<input type="password" name="password" />
<br />
<input type="submit" name="submit">
</form>
使用session隨機(jī)碼驗(yàn)證投票合法性:
代碼如下:
list.php 選項(xiàng)頁(yè)面
session_start();
$tokenKey = md5(rand(1,100));
$_SESSION['tokenKey'] = $tokenKey;
注意:在傳值時(shí)同時(shí)傳入隨機(jī)碼$tokenKey
vote.php 投票動(dòng)作執(zhí)行頁(yè)面
$tokenKey = $_SESSION['tokenKey'];
if($_POST['tokenKey'] != $tokenKey){ //判斷隨機(jī)碼是否和上一頁(yè)相同
echo "<script>alert('請(qǐng)重新投票!');location.href='list.php';</script>"; //隨機(jī)碼無(wú)效
exit;
}else{
執(zhí)行投票操作;
清空session存儲(chǔ)的隨機(jī)碼
}
更多信息請(qǐng)查看IT技術(shù)專欄