最近用到php中session時,忽然發現php中的session有點讓人頭疼啊,要設置一個嚴格的特定時間內過期的session還真不太容易!
后來在網上查詢時,發現這個問題還真是有點普遍,網上也有關於這個問題的面試問題,如:如何嚴格限制session在30分鍾后過期!這個問題的答案順便也寫在這里
1.設置客戶端cookie的lifetime為30分鍾;
2.設置session的最大存活周期也為30分鍾;
3.為每個session加入時間戳,然后在程序調用時進行判斷;
至於為什么,我們首先來了解下php中session的基本原理:
PHP中的session有效期默認是1440秒(24分鍾),也就是說,客戶端超過24分鍾沒有刷新,當前session就會失效。當然如果用戶關閉了瀏覽器,回話也就結束了,Session自然也不存在
了!
大家知道,Session儲存在服務器端,根據客戶端提供的SessionID來得到這個用戶的文件,然后讀取文件,取得變量的值,SessionID可以使用客戶端的Cookie或者Http1.1協議的
Query_String(就是訪問的URL的“?”后面的部分)來傳送給服務器,然后服務器讀取Session的目錄……
要控制Session的生命周期,首先我們需要了解一下php.ini關於Session的相關設置(打開php.ini文件,在“[Session]”部分):
1、session.use_cookies:默認的值是“1”,代表SessionID使用Cookie來傳遞,反之就是使用Query_String來傳遞;
2、session.name:這個就是SessionID儲存的變量名稱,可能是Cookie,也可能是Query_String來傳遞,默認值是“PHPSESSID”;
3、session.cookie_lifetime:這個代表SessionID在客戶端Cookie儲存的時間,默認是0,代表瀏覽器一關閉SessionID就作廢……就是因為這個所以Session不能永久使用!
4、session.gc_maxlifetime:這個是Session數據在服務器端儲存的時間,如果超過這個時間,那么Session數據就自動刪除!
還有很多的設置,不過和本文相關的就是這些了,下面開始講如何設置Session的存活周期。
前面說過,服務器通過SessionID來讀取Session的數據,但是一般瀏覽器傳送的SessionID在瀏覽器關閉后就沒有了,那么我們只需要人為的設置SessionID並且保存下來,不就可以……
如果你擁有服務器的操作權限,那么設置這個非常非常的簡單,只是需要進行如下的步驟:
1、把“session.use_cookies”設置為1,使用Cookie來儲存SessionID,不過默認就是1,一般不用修改;
2、把“session.cookie_lifetime”改為你需要設置的時間(比如一個小時,就可以設置為3600,以秒為單位);
3、把“session.gc_maxlifetime”設置為和“session.cookie_lifetime”一樣的時間;
在PHP的文檔中明確指出,設定session有效期的參數是session.gc_maxlifetime。可以在php.ini文件中,或者通過ini_set()函數來修改這一參數。問題在於,經過多次測試,修改這個
參數基本不起作用,session有效期仍然保持24分鍾的默認值。
由於PHP的工作機制,它並沒有一個daemon線程,來定時地掃描session信息並判斷其是否失效。當一個有效請求發生時,PHP會根據全局變量
session.gc_probability/session.gc_divisor(同樣可以通過php.ini或者ini_set()函數來修改)的值,來決定是否啟動一個GC(Garbage Collector)。
默認情況下,session.gc_probability = 1,session.gc_divisor =100,也就是說有1%的可能性會啟動GC。GC的工作,就是掃描所有的session信息,用當前時間減去session的最后修
改時間(modified date),同session.gc_maxlifetime參數進行比較,如果生存時間已經超過gc_maxlifetime,就把該session刪除。
到此為止,工作一切正常。那為什么會發生gc_maxlifetime無效的情況呢?
在默認情況下,session信息會以文本文件的形式,被保存在系統的臨時文件目錄中。在Linux下,這一路徑通常為\tmp,在 Windows下通常為C:\Windows\Temp。當服務器上有多個PHP應
用時,它們會把自己的session文件都保存在同一個目錄中。同樣地,這些PHP應用也會按一定機率啟動GC,掃描所有的session文件。
問題在於,GC在工作時,並不會區分不同站點的session。舉例言之,站點A的gc_maxlifetime設置為2小時,站點B的 gc_maxlifetime設置為默認的24分鍾。當站點B的GC啟動時,它會掃
描公用的臨時文件目錄,把所有超過24分鍾的session文件全部刪除掉,而不管它們來自於站點A或B。這樣,站點A的gc_maxlifetime設置就形同虛設了。
找到問題所在,解決起來就很簡單了。修改session.save_path參數,或者使用session_save_path()函數,把保存session的目錄指向一個專用的目錄,gc_maxlifetime參數工作正常了。
還有一個問題就是,gc_maxlifetime只能保證session生存的最短時間,並不能夠保存在超過這一時間之后session信息立即會得到刪除。因為GC是按機率啟動的,可能在某一個長時間內
都沒有被啟動,那么大量的session在超過gc_maxlifetime以后仍然會有效。
解決這個問題的一個方法是,把session.gc_probability/session.gc_divisor的機率提高,如果提到100%,就會徹底解決這個問題,但顯然會對性能造成嚴重的影響。另一個方法是自己
在代碼中判斷當前session的生存時間,如果超出了 gc_maxlifetime,就清空當前session。
另外在查看php手冊時發現,手冊本身提供了一個函數用來管理session,可以有效的解決session定時過期問題,
<?php class FileSessionHandler { private $savePath; function open($savePath, $sessionName) { $this->savePath = $savePath; if (!is_dir($this->savePath)) { mkdir($this->savePath, 0777); } return true; } function close() { return true; } function read($id) { return (string)@file_get_contents(“$this->savePath/sess_$id”); } function write($id, $data) { return file_put_contents(“$this->savePath/sess_$id”, $data) === false ? false : true; } function destroy($id) { $file = “$this->savePath/sess_$id”; if (file_exists($file)) { unlink($file); } return true; } function gc($maxlifetime) { foreach (glob(“$this->savePath/sess_*”) as $file) { if (filemtime($file) + $maxlifetime < time() && file_exists($file)) { unlink($file); } } return true; } } $handler = new FileSessionHandler(); session_set_save_handler( array($handler, ‘open’), array($handler, ‘close’), array($handler, ‘read’), array($handler, ‘write’), array($handler, ‘destroy’), array($handler, ‘gc’) ); // the following prevents unexpected effects when using objects as save handlers register_shutdown_function(‘session_write_close’); session_start(); // proceed to set and retrieve values by key from $_SESSION ?>
PS:也需有的童鞋會有一些疑惑,為什么定義的時候read 、write等方法都有參數,但是用session_set_save_handler函數調用的時候卻沒有加入任何參數,這是因為這些參數本身是不用我
們手動傳入的,而是由php自動傳入,可以理解為php會從php.ini里讀取對應的設置,然后將對應的值傳入對應方法!所以我們不用擔心這些參數,只需要按照這些函數的格式,和參數個數將
方法寫好就OK!
2.
大多數據情況下我們對於session過期時間使用的是默認設置的時間,而對於一些有特殊要求的情況下我們可以設置一下session過期時間。
對此,可以在PHP中,設置php.ini,找到session.gc_maxlifetime = 1440 #(PHP5默認24分鍾)
這里你可以隨便設置一下過期時間.但是有人說設置以后,好象不起作用!
其實不是不起作用,而是因為系統默認:
1 |
session.gc_probability = 1 |
2 |
session.gc_divisor = 1000 |
garbage collection 有個概率的,1/1000就是session 1000次才有一次被回收。
只要你的訪問量大了,那就能達到回收的效果.
或者你也可以設置一下session.gc_divisor 的值,
比如:session.gc_divisor = 1,這樣就能明顯的看到SESSION過期的效果了.
我們最常用的是在php程序中設置,如下例程序所示:
1 |
<?php |
2 |
if (!isset( $_SESSION [ 'last_access' ])||(time()- $_SESSION [ 'last_access' ])>60) |
3 |
$_SESSION [ 'last_access' ] = time(); |
4 |
?> |
這樣就搞定了,如果要設置已過期的話也可以在程序中實現:
1 |
<?php |
2 |
unset( $_SESSION [ 'last_access' ]); // 或 $_SESSION['last_access']=''; |
3 |
?> |
session有過期的機制:
session.gc_maxlifetime 原來session 過期是一個小概率的事件,分別使用session.gc_probability和session.gc_divisor 來確定運行session 中gc 的概率 session.gc_probability和session.gc_divisor的默認值分別為 1和100。分別為分子和分母 所以session中gc的概率運行機會為1% 。如果修改這兩個值,則會降低php的效率。所以這種方法是不對的!!
因此,修改php.ini文件中的gc_maxlifetime變量就可以延長session的過期時間了:(例如,我們把過期時間修改為86400秒)
session.gc_maxlifetime = 86400
然后,重啟你的web服務(一般是apache)就可以了。
session“回收”何時發生:
默認情況下,每一次php請求,就會有1/100的概率發生回收,所以可能簡單的理解為“每100次php請求就有一次回收發生”。這個概率是通過以下參數控制的
#概率是gc_probability/gc_divisor
1 |
session.gc_probability = 1 |
2 |
session.gc_divisor = 100 |
注意1:假設這種情況gc_maxlifetime=120,如果某個session文件最后修改時間是120秒之前,那么在下一次回收(1/100的概率)發生前,這個session仍然是有效的。
注意2:如果你的session使用session.save_path中使用別的地方保存session,session回收機制有可能不會自動處理過期session文件。這時需要定時手動(或者crontab)的刪除過期的session:
1 |
cd /path/to/sessions; find -cmin +24 | xargs rm |
PHP中的session永不過期
不修改程序是最好的方法了,因為如果修改程序,測試部一定非常郁悶,那么只能修改系統環境配置,其實很 簡單,打開php.ini設置文件,修改三行如下:
1、session.use_cookies
把這個的值設置為1,利用cookie來傳遞sessionid
2、session.cookie_lifetime
這個代表SessionID在客戶端Cookie儲存的時間,默認是0,代表瀏覽器一關閉SessionID就作廢……就是因為這個所以PHP的 session不能永久使用! 那么我們把它設置為一個我們認為很大的數字吧,999999999怎么樣,可以的!就這樣。
3、session.gc_maxlifetime
這個是Session數據在服務器端儲存的時間,如果超過這個時間,那么Session數據就自動刪除! 那么我們也把它設置為99999999。
就這樣一切ok了,當然你不相信的話就測試一下看看——設置一個session值過個10天半個月的回來看看,如果你的電腦沒有斷電或者宕機,你仍然可以看見這個sessionid。
當然也可能你沒有控制服務器的權限並不能像我一樣幸運的可以修改php.ini設置,一切依靠我們自己也是有辦法的,當然就必須利用到客戶端存儲 cookie了,把得到的sessionID存儲到客戶端的cookie里面,設置這個cookie的值,然后把這個值傳遞給session_id()這 個函數
session失效不傳遞
我們先寫個php文件:<?=phpinfo()?>, 傳到服務器去看看服務器的參數配置。
轉到session部分,看到session.use_trans_sid參數被設為了零。
這個參數指定了是否啟用透明SID支持,即session是否隨着URL傳遞。我個人的理解是,一旦這個參數被設為0,那么每個URL都會啟一個session。這樣后面頁面就無法追蹤得到前面一個頁面的session,也就是我們所說的無法傳遞。兩個頁面在服務器端生成了兩個session文件,且無關聯。(此處精確原理有待確認)
所以一個辦法是在配置文件php.ini里把session.use_trans_sid的值改成1。
當然我們知道,不是誰都有權限去改php的配置的,那么還有什么間接的解決辦法呢?
下面就用兩個實例來說明:
文件1 test1.php
01 |
<?php |
02 |
//表明是使用用戶ID為標識的session |
03 |
session_id(SID); |
04 |
//啟動session |
05 |
session_start(); |
06 |
//將session的name賦值為Havi |
07 |
$_SESSION [ 'name' ]= "Havi" ; |
08 |
//輸出session,並設置超鏈接到第二頁test2.php |
09 |
echo "<a href=" test2.php " rel=" external nofollow " >" . $_SESSION [ 'name' ]. "</a>" ; |
10 |
?> |
文件2: test2.php
1 |
<?php |
2 |
表明是使用用戶ID為標識的session |
3 |
session_id(SID); |
4 |
//啟動session |
5 |
session_start(); |
6 |
//輸出test1.php中傳遞的session。 |
7 |
echo "This is " . $_SESSION [ 'name' ]; |
8 |
?> |
所以,重點是在session_start();前加上session_id(SID);,這樣頁面轉換時,服務器使用的是用戶保存在服務器session文件夾里的session,解決了傳遞的問題。
不過有朋友會反映說,這樣一來,多個用戶的session寫在一個SID里了,那Session的價值就發揮不出來了。所以還有一招來解決此問題,不用加session_id(SID);前提是你對服務器的php.ini有配置的權限:
output_buffering改成ON,道理就不表了。
第二個可能的原因是對服務器保存session的文件夾沒有讀取的權限,還是回到phpinfo.php中,查看session保存的地址:
1 |
session.save_path: var /tmp |
所以就是檢查下var/tmp文件夾是否可寫。
寫一個文件:test3.php來測試一下:
1 |
<?php |
2 |
echo var_dump( is_writeable ( ini_get ( "session.save_path" ))); |
3 |
?> |
如果返回bool(false),證明文件夾寫權限被限制了,那就換個文件夾咯,在你編寫的網頁里加入:
1 |
//設置當前目錄下session子文件夾為session保存路徑。 |
2 |
$sessSavePath = dirname( __FILE__ ). '/session/' ; |
3 |
//如果新路徑可讀可寫(可通過FTP上變更文件夾屬性為777實現),則讓該路徑生效。 |
4 |
if ( is_writeable ( $sessSavePath ) && is_readable ( $sessSavePath )) |
5 |
{ |
6 |
session_save_path( $sessSavePath ); |
7 |
} |
3.
概述:每一次php請求,會有1/100的概率(默認值)觸發“session回收”。如果“session回收”發生,那就會檢查/tmp/sess_*的文件,如果最后的修改時間到現在超過了1440秒(gc_maxlifetime的值),就將其刪除,意味着這些session過期失效。
1. session在server端(一般是Apache with PHP module)如何存在的?
默認的,php會將session保存在/tmp目錄下,文件名為這個樣子:sess_01aab840166fd1dc253e3b4a3f0b8381。每一個文件對應了一個session(會話)。
more /tmp/sess_01aab840166fd1dc253e3b4a3f0b8381
username|s:9:”jiangfeng”;admin|s:1:”0″;
#變量名|類型:長度:值
刪除這里的session文件,就表示對應的session失效了。
2. session在client端(一般是瀏覽器)如何存在的?
session在瀏覽器端,只需要保存session ID(由server端生成的唯一ID)就可以了。有兩種保存方式:在cookie中、在url里面。如果cookie中保存session ID,就可以看到瀏覽器的cookie中有一個PHPSESID變量。如果是URL傳遞的,就可以看到形如:
index.php?PHPSESID=01aab840166fd1dc253e3b4a3f0b8381的URL。(在server端通過session.use_cookies來控制使用哪一種方式)
3. 在server端,php如何判斷session文件是否過期?
如果”最后的修改時間”到”現在”超過了gc_maxlifetime(默認是1440)秒,這個session文件就被認為是過期了,在下一次session回收的時候,如果這個文件仍然沒有被更改過,這個session文件就會被刪除(session就過期了)。
簡單的說,如果我登錄到某網站,如果在1440秒(默認值)內沒有操作過,那么對應的session就認為是過期了。
所以,修改php.ini文件中的gc_maxlifetime變量就可以延長session的過期時間了:(例如,我們把過期時間修改為86400秒)
session.gc_maxlifetime = 86400
然后,重啟你的web服務(一般是apache)就可以了。
注意:php5里面session過期使用了回收機制。這里設置時間為86400秒,如果session在86400秒內沒有被修改過,那么在下一次“回收”時才真的被刪除。
3. session“回收”何時發生?
默認情況下,每一次php請求,就會有1/100的概率發生回收,所以可能簡單的理解為“每100次php請求就有一次回收發生”。這個概率是通過以下參數控制的
#概率是gc_probability/gc_divisor
session.gc_probability = 1
session.gc_divisor = 100
注意1:假設這種情況gc_maxlifetime=120,如果某個session文件最后修改時間是120秒之前,那么在下一次回收(1/100的概率)發生前,這個session仍然是有效的。
注意2:如果你的session使用session.save_path中使用別的地方保存session,session回收機制有可能不會自動處理過期session文件。這時需要定時手動(或者crontab)的刪除過期的session:cd /path/to/sessions; find -cmin +24 | xargs rm
4. 一些特殊情況
因為回收機制會檢查文件的“最后修改時間”,所以如果某個會話是活躍的,但是session的內容沒有改變過,那么對應的session文件也就沒有改變過,回收機制會認為這是一個長時間沒有活躍的session而將其刪除。這是我們不願看到的,可以通過增加如下的簡單代碼解決這個問題:
<?php
if(!isset($_SESSION['last_access'])||(time()-$_SESSION['last_access'])>60)
$_SESSION['last_access'] = time();
?>代碼會每隔60秒,嘗試修改修改一次session。
總結:如果想修改session過期時間,修改變量gc_maxlifetime就可以了。php5的session采用被動的回收機制(garbage collection)。過期的session文件不會自己消失,而是通過觸發“回收”來處理過期的session。