Session技術和Cookie類似,都是用來儲存使用者的相關資料。但最大的不一樣之處在於Cookie是將數據存放在客戶端的計算機之中,而Session則是將數據存放於服務器系統之下。Session的中文意思是會話,在Web系統中,一般是指用戶與Web系統的對話過程。本文將詳細介紹Session的內容php
在Web技術發展史上,雖然Cookie技術的出現是一個重大的變革,但Cookie是在客戶端的計算機中保存資料,因此引發了一個爭議。用戶有權阻止Cookie的使用,使Web服務器沒法經過Cookie來跟蹤用戶信息。而Session技術是將使用者相關的資料存放在服務器的系統之下,因此使用者沒法中止Session的使用html
Session在客戶端僅須要保存由服務器爲用戶建立的一個Session標識符,稱爲Session ID,而在服務器端(文件或數據庫或MemCache中)保存Session變量的值。Session ID是一個既不會重複,又不容易被找到規律的,由32位16進制數組成的字符串mysql
Session ID會保存在客戶端的Cookie中,若是用戶阻止Cookie的使用,則能夠將Session ID保存在用戶瀏覽器地址欄的URL中。當用戶請求Web服務器時,就會把Session ID發送給服務器,再經過Session ID提取保存在服務器中的Session變量。能夠把Session中保存的變量,當作是這個用戶的全局變量,同一個用戶對每一個腳本的訪問都共享這些變量web
當某個用戶向Web服務器發出請求時,服務器首先會檢查這個客戶端的請求裏是否已經包含了一個Session ID。若是包含,說明以前已經爲此用戶建立過Session,服務器則按該Session ID把Session檢索出來使用。若是客戶端請求不包含Session ID,則爲該用戶建立一個Session,而且生成一個與此Session相關聯的Session ID,在本次響應中被傳送給客戶端保存sql
【session_start()】數據庫
用戶向Web服務器發出請求時,必須首先使用session_start()函數來啓動新會話或者重用現有會話,成功開始會話返回TRUE,反之返回FALSE數組
bool session_start ([ array $options = [] ] )
由於基於Cookie的Session是在開啓的時候,調用session_start()函數會生成一個惟一的SessionID,須要保存在客戶端電腦的Cookie中,和setCookie()函數同樣,調用以前不能有任何的輸出,空格或空行也不行瀏覽器
若是已經開啓過Session,再次調用Session_start()函數時,不會再建立個新的Session ID。由於當用戶再次訪問服務器時,該函數會經過從客戶端攜帶過來的Session ID,返回已經存在的Session。因此在會話期間,同一個用戶在訪問服務器上任何一個頁面時,都是使用同一個 Session ID緩存
session_start();
並且,使用session_start()方法會在服務器端創建一個同名的Session文件(文本文件)安全
若是不想在每一個腳本都使用Session_start()函數來開啓Session,能夠在php.ini裏設置"session.auto_start=1",則無須每次使用Session以前都要調用session_start()函數。但啓用該選項也有一些限制,則不能將對象放入Session中,由於類定義必須在啓動Session以前加載。因此不建議使用php.ini中的session.auto_start屬性來開啓Session
使用session_start()方法啓動session會話後,要經過訪問$_SESSION數組來讀寫session。和$_POST、$_GET、$_COOKIE相似,$_SESSION也是超全局數組
使用$_SESSION數組將數據存入同名Session文件中
<?php session_start(); $_SESSION['username'] = 'huochai'; $_SESSION['age'] = 28; ?>
同名Session文件能夠直接使用文本編輯器打開,該文件的內容結構以下所示:
變量名|類型:長度:值;
<?php session_start(); print_r ($_SESSION);//Array ( [username] => huochai [age] => 28 ) ?>
Session變量會被保存在服務器端的某個文件中,該文件的位置是經過php.ini文件,在session.save_path屬性指定的目錄下
在PHP配置文件php.ini中,有一組和Session相關的配置選項。經過對一些選項從新設置新值,就能夠對Session進行配置,不然使用默認的Session配置
phpinfo();
session.auto_start=0;在請求啓動時初始化session session.cache_expire=180;設置緩存中的會話文檔在n分鐘後過期 session.cookie_lifetime=0;設置cookie保存時間(s),至關於設置Session過時時間,爲0時表示直到瀏覽器被重啓 session.cookie_path=/;cookie的有效路徑 session.cookie_domain=;cookie的有效域 session.name=PHPSESSID;用在cookie裏的session的名字 session.save_handler=files;用於保存/取回數據的控制方式 session.save_path=/tmp;在save_handler設爲文件時傳給控制器的參數,這是數據文件將保存的路徑. session.use_cookies=1;是否使用cookies
當使用完一個Session變量後,能夠將其刪除,當完成一個會話後,也能夠將其銷燬。若是用戶想退出Web系統,就須要提供一個註銷的功能,把全部信息在服務器中銷燬。銷燬和當前Session有關的全部的資料,能夠調用session_destroy()函數結束當前的會話,並清空會話中的全部資源
【session_destroy()】
bool session_destroy ( void )
session_destroy()銷燬當前會話中的所有數據,刪除同名Session文件,可是不會重置當前會話所關聯的全局變量,也不會重置會話cookie。若是須要再次使用會話變量,必須從新調用session_start()函數
<?php session_start(); session_destroy(); ?>
可使用unset()函數來釋放在Session中註冊的單個變量
print_r ($_SESSION);//'Array ( [username] => huochai [age] => 28 )' unset($_SESSION['username']); unset($_SESSION['age']); print_r ($_SESSION);//'Array()'
[注意]不要使用unset($_SESSION)刪除整個$_SESSION數組,這樣將不能再經過$_SESSION超全局數組註冊變量了
若是想把某個用戶在Session中註冊的全部變量都刪除,能夠直接將數組變量$_SESSION賦值爲一個空數組
$_SESSION=array();
PHP默認的Session是基於Cookie的,Session ID被服務器存儲在客戶端的Cookie中,因此在註銷Session時也須要清除Cookie中保存的SessionID,而這就必須藉助setCookie()函數完成。在Cookie中,保存Session ID的Cookie標識名稱就是Session的名稱,這個名稱是在php.ini中,經過session.name屬性指定的值。在PHP腳本中,能夠經過調用session_name()函數獲取Session名稱。刪除保存在客戶端Cookie中的Session ID
if(isset($_COOKIE[session_name()])) { setCookie(session_name(),'',time()-3600); }
經過前面的介紹能夠總結出來,Session的註銷過程共須要四個步驟
<?php //第一步:開啓Session並初始化 session_start(); //第二步:刪除全部Session的變量,也可用unset($_SESSION[xxx])逐個刪除 $_SESSION = array(); //第三步:若是使用基於Cookie的Session,使用setCooike()刪除包含Session Id的Cookie if (isset($_COOKIE[session_name()])) { setcookie(session_name(),'', time()-42000); } //第四步:最後完全銷燬Session,刪除服務器端保留session信息的文件 session_destroy(); ?>
若是沒有經過上述步驟銷燬Session,而是直接關閉瀏覽器,或斷網等狀況,在服務器端保存的Session文件是不會被刪除的。由於在php.ini配置文件中,默認的session.cookie_lifetime=0,表示Session ID在客戶端Cookie的有效期限爲直到關閉瀏覽器。Session ID消失了,但服務器端保存的Session文件並無被刪除。因此,沒有被Session ID關聯的服務器端Session文件成爲了垃圾,而系統則提供了自動清理的機制
服務器保存的Session文件是普通文本文件,都有文件修改時間。經過在php.ini配置文件中設置session.gc_maxlifetime選項來設置一個到期時間(默認爲1440秒,即24分鐘)。垃圾回收程序在全部Session文件中排查出大於24分鐘的文件。若是用戶還在使用該文件,那麼這個Session文件的修改時間就會被更新,將不會被排查到
排除出來後,並不會馬上清理垃圾,而是根據配置文件php.info中session.gc_probability/session.gc_divisor這兩個值的比例來決定什麼時候清理,默認值是1/100。表示排查100次,纔有一次可能會啓動垃圾回收機制,來自動回收垃圾。固然,這個值是能夠修改的,但仍是要兼顧服務器的運行性能和存儲空間
使用Session跟蹤一個用戶,是經過在各個頁面之間傳遞惟一的Session ID,並經過Session ID提取這個用戶在服務器中保存的Session變量。常見的Session ID傳送方法有如下兩種
一、基於Cookie的方式傳遞Session ID。這種方法更優化,但因爲不老是可用,由於用戶在客戶端能夠屏蔽Cookie
二、經過URL參數進行傳遞,直接將會話ID嵌入到URL中去
在Session的實現中一般都是採用基於Cookie的方式,客戶端保存的Session ID就是一個Cookie。當客戶禁用Cookie時,Session ID就不能再在Cookie中保存,也就不能在頁面之間傳遞,此時Session失效。不過PHP5在Linux平臺能夠自動檢查Cookie狀態,若是客戶端將它禁用,則系統自動把Session ID附加到URL上傳送。而使用Windows系統做爲Web服務器則無此功能
【經過Cookie傳遞Session ID】
若是客戶端沒有禁用Cookie,則在PHP腳本中經過session_start()函數進行初始化後,服務器會自動發送HTTP標頭將Session ID保存到客戶端電腦的Cookie中
相似於下面的設置方式
//虛擬向Cookie中設置Session ID的過程 setCookie(session_name(),session_id(),0,'/')
第一個參數中調用session_name()函數,返回當前Session的名稱做爲Cookie的標識名稱。Session名稱的默認值爲PHPSESSID,是在php.ini文件中由session.name選項指定的值。也能夠在調用session_name()函數時提供參數改變當前Session的名稱
echo session_name();//PHPSESSID
第二個參數中調用session_id()函數,返回當前Session ID做爲Cookie的值。也能夠經過調用session_id()函數時提供參數設定當前Session ID
echo session_id();//kstvdmae177qqk6jgvg6td12l1
第三個參數的值0,是經過在php.ini文件中由session.cookiejifetime選項設置的值。默認值爲0,表示SessIon ID將在客戶機的Cookie中延續到瀏覽器關閉
最後一個參數'/',也是經過PHP配置文件指定的值,在php.ini中由session.cookie.path選項設置的值。默認值爲'/',表示在Cookie中要設置的路徑在整個域內都有效
若是服務器成功將Session ID保存在客戶端的Cookie中,當用戶再次請求服務器時,就會把Session ID發送回來。因此當在腳本中再次使用session_start()函數時,就會根據Cookie中的Session ID返回已經存在的Session
【經過URL傳遞Session ID】
若是客戶瀏覽器支持Cookie,就把Session ID做爲Cookie保存在瀏覽器中。但若是客戶端禁止Cookie的使用,瀏覽器中就不存在做爲Cookie的Session ID,所以在客戶請求中不包含Cookie信息。若是調用session_start()函數時,沒法從客戶端瀏覽器中取得做爲Cookie的Session ID,則又建立了一個新的Session ID,也就沒法跟蹤客戶狀態。所以,每次客戶請求支持Session的PHP腳本,session_start()函數在開啓Session時都會建立一個新的Session,這樣就失去了跟蹤用戶狀態的功能
若是客戶瀏覽器不支持Cookie,PHP則能夠重寫客戶請求的URL,把Session ID添加到URL信息中。能夠手動地在每一個超連接的URL中都添加一個Session ID,但工做量比較大,不建議使用這種方式。以下所示:
<?php session_start(); echo '<a href="demo.php?'.session_name().'='.session_id() .'">連接演示</a>'; ?>
在使用Linux系統作服務器時,而且選用PHP4.2之後的版本,則在編輯PHP時若是使用了-enable-trans-sid配置選項,和運行時選項session.use_trans_sid都被激活,在客戶端禁用Cookie時,相對URL將被自動修改成包含會話ID。若是沒有這麼配置,或者使用Windows系統做爲服務器時,可使用常量SID。該常量在會話啓動時被定義,若是客戶端沒有發送適當的會話Cookie,則SID的格式爲session_name=session_id,不然就爲一個空字符串。所以能夠無條件地將其嵌入到URL中去。以下所示
//當阻止cookie時,SID返回'PHPSESSID=p2qouo8hjarul0a0ii5jmocmc0',不然返回一個空字符串 echo SID;
<?php session_start(); $_SESSION["usemame"]="admin"; echo "Session ID:".session_id()."<br>"; ?> <a href="test2.php?<?php echo SID ?>">經過URL傳遞Session ID</a>
若是使用Linux系統做爲服務器,並配置好相應的選項,就不用手動在每一個URL後面附加SID,相對URL將被自動修改成包含Session ID。但要注意,非相對的URL被假定爲指向外部站點,所以不能附加SID。由於這多是個安全隱患,會將SID泄露給不一樣的服務器
在系統中使用Session技術跟蹤用戶時,Session默認的處理方式是使用Web服務器中的文件來記錄每一個用戶的會話信息,經過php.ini中的session_save_path建立會話數據文件的路徑。這種默認的處理方式雖然很方便,但也有一些缺陷。例如,登陸用戶若是很是大,文件操做的I/O開銷就會很大,會嚴重影響系統的執行效率。另外,最主要的是自己的session機制不能跨機,由於對於訪問量比較大的系統,一般都是採用多臺web服務器進行併發處理,若是每臺web服務器都各自獨立地處理Session,就不可能達到跟蹤用戶的目的。這時就須要改變session的處理方式,常見的跨機方法就是經過自定義session的存儲方式,能夠將session信息使用NFS或SAMBA等共享技術保存到其餘服務器中,或使用數據庫來保存session信息,最優的方式是使用memcached來進行session存儲
不管是用memcached、數據庫、仍是經過NFS或SAMBA共享session信息,其原理是同樣的,都是經過PHP中的session_set_save_handler()函數來改變默認的處理方式,指定回調函數來自定義處理
Session_set_save_hander(callback open,callback close,call read,callback write,callback destro,callback gc);
該函數共須要6個回調函數做爲必選參數,分別表明了Session生命週期中的6個過程,用戶經過自定義每一個函數,來設置Session生命週期中每一個環節的信息處理
回調函數的執行時機以下所示
回調函數 描述 open 運行session_start()時執行,該函數須要聲明兩個參數,系統自動將php.ini中的session_save_path選項值傳遞給該函數的第一個參數,將Session名自動傳遞給第二個參數中,返回true則能夠繼續向下執行 close 該函數不須要參數,在腳本執行完成或調用session_write_close()、session_destroy()時被執行,即在全部session操做完成後被執行。若是不須要處理,則直接返回true便可 read 在運行session_start()時執行,由於在開啓會話時,會read當前session數據並寫入$_SESSION變量。須要聲明一個參數,系統會自動將Session ID傳遞給該函數,用於經過Session ID獲取對應的用戶數據,返回當前用戶的會話信息寫入$_SESSION變量 write 該函數在腳本結束和對$_SESSION變量賦值數據時執行。須要聲明兩個參數,分別是Session ID和串行化後Session信息字符串。在對$_SESSION變量賦值時,就能夠經過Session ID找到存儲的位置,並將信息寫入。存儲成功能夠返回true繼續向下執行 destroy 在運行session_destroy()時執行,須要聲明一個參數,系統會自動將Session ID傳遞給該函數,去刪除對應的會話信息 gc 垃圾回收程序啓動時執行。須要聲明一個參數,系統自動將php.ini中的session_gc_maxlifetime選項值傳給該函數,用於刪除超過這個時間的Session信息,返回true則能夠繼續向下執行
在運行session_start()時分別執行了open(啓動會話)、read(讀取session數據至$_SESSION)和gc(清理垃圾),腳本中全部對$_SESSION的操做均不會調用這些回調函數。在調用session_destroy()函數時,執行destroy銷燬當前session(通常是刪除相應的記錄或文件),但此回調函數銷燬的只是Session的數據,此時若是輸出$_SESSION變量,仍然有值,但此值不會再close後被寫回去。在調用session_write_close()函數時執行write和close,保存$_SESSION至存儲,若是不手工使用此方法,則會在腳本結束時被自動執行
[注意]session_set_save_hander()函數必須在php.ini中設置session_save_hander選項的值爲」user」時(用戶自定義處理器),纔會被系統調用
<?php $sess_save_path =""; function open($save_path,$session_name){ global $sess_save_path; $sess_save_path = $save_path; return true; } function close(){ return true; } function read($id){ global $sess_save_path; $sess_file ="{$sess_save_path}/sess_{$id}"; return (string) @file_get_contents($sess_file); } function write($id,$sess_data){ global $sess_save_path; $sess_file ="{$sess_save_path}/sess_{$id}"; if($fp=@fopen($sess_file,"w")){ $return = fwrite($fp,$sess_data); fclose($fp); return $return; }else{ return false; } } function destroy($id){ global $sess_save_path; $sess_file ="{$sess_save_path}/sess_{$id}"; return (@unlink($sess_file)); } function gc($maxlifetime){ global $sess_save_path; foreach(glob("{$sess_save_path}/sess_*") as $filename){ if(filemtime($filename) + $maxlifetime <time() ){ @unlink($filename); } } return true; } session_set_save_hander(「open","close","read","write","destroy","gc"); session_start(); ?>
若是網站訪問量很是大,須要採用負載均衡技術搭載多臺Web服務器協同工做,就須要進行Session同步處理。使用數據庫處理Session會比使用NFS及SAMBA更佔優點,能夠專門創建一個數據庫服務器存放Web服務器的Session信息,當用戶無論訪問集羣中的哪一個Web服務器,都會去這個專門的數據庫,訪問本身在服務器端保存的Session信息,以達到Session同步的目的。另外,使用數據庫處理Session還能夠給咱們帶來不少好處,好比統計在線人數等。若是mysql也作了集羣,每一個mysql節點都要有這張表,而且這張Session表的數據要實時同步
在使用默認的文件方式處理Session時,有3個比較重要的屬性,分別是文件名稱、文件內容及文件的修改時間:經過文件名稱中包含的Session ID,用戶能夠找到本身在服務器端的Session文件;經過文件內容用戶能夠在各個腳本中存取$_session變量;經過文件的修改時間則能夠清除全部過時的Session文件。因此使用數據表處理Session信息,也最少要有這三個字段(Session ID、修改時間、Session內容信息),固然若是考慮更多的狀況,例如,用戶改變了IP地址,用戶切換了瀏覽器等,還能夠再自定義一些其餘字段。下面爲Session設計的數據表結構包含5個字段,建立保存Session信息表session的SQL語句以下所示:
CREATE TABLE session( sid CHAR(32) NOT NULL DEFAULT '', update INT NOT NULL DEFAULT 0, client_ip CHAR(15) NOT NULL DEFAULT '', user_agent CHAR(200) NOT NULL DEFAULT '', data TEXT, PRIMARY KEY(sid) );
數據表session建立成功後,再經過自定義的處理方式,將Session信息寫入到數據庫中
<?php class DBSession { public static $pdo; //pdo的對象 public static $ctime; //當前時間 public static $maxlifetime; //最大的生存時間 public static $uip; //用戶正在用的ip public static $uagent; //用戶正在用的瀏覽器 //開啓和初使化使用的, 參數須要一個路 public static function start(PDO $pdo) { self::$pdo = $pdo; self::$ctime = time(); self::$maxlifetime = ini_get("session.gc_maxlifetime"); self::$uip = !empty($_SERVER['HTTP_CLIENT_IP']) ? $_SERVER['HTTP_CLIENT_IP'] : (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : (!empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : "") ); filter_var(self::$uip, FILTER_VALIDATE_IP) && self::$uip = ''; self::$uagent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "" ; //註冊過程, 讓PHP本身處理session時,找這個函數指定的幾個週期來完成 session_set_save_handler( array(__CLASS__, "open"), array(__CLASS__,"close"), array(__CLASS__, "read"), array(__CLASS__, "write"), array(__CLASS__, "destroy"), array(__CLASS__,"gc")); session_start(); //開啓會話 } // 開啓時, session_start() public static function open($path, $name) { return true; } //關閉 public static function close() { return true; } //讀取 echo $_SESSION['username'] public static function read($sid) { $sql = "select * from session where sid = ?"; $stmt = self::$pdo -> prepare($sql); $stmt -> execute(array($sid)); $result = $stmt -> fetch(PDO::FETCH_ASSOC); //若是尚未會話信息,返回空字符串 if(!$result) { return ''; } //若是超出時間,銷燬session if($result['utime'] + self::$maxlifetime < self::$ctime) { self::destroy($sid); return ''; } //若是用戶換了ip或換了瀏覽器 if($result['uip'] != self::$uip || $result['uagent'] != self::$uagent) { self::destroy($sid); return ''; } return $result['sdata']; } //寫入 $_SESSION['username'] = "meizi"; public static function write($sid, $data) { //經過sid獲取已經有的數據 $sql = "select * from session where sid = ?"; $stmt = self::$pdo->prepare($sql); $stmt -> execute(array($sid)); $result = $stmt -> fetch(PDO::FETCH_ASSOC); //若是已經獲取到了數據,就不插入而更新 if($result) { //若是數據和原來的不一樣才更新 if($result['sdata'] != $data || $result['utime']+10 < self::$ctime) { $sql = "update session set sdata = ?, utime = ? where sid=?"; $stmt = self::$pdo->prepare($sql); $stmt -> execute(array($data, self::$ctime, $sid)); } //若是沒有數據,就新插入一條數據 } else { if(!empty($data)) { $sql = "insert into session(sid, sdata, utime, uip, uagent) values(?, ?, ?, ?, ?)"; $stmt = self::$pdo -> prepare($sql); $stmt -> execute(array($sid, $data, self::$ctime, self::$uip, self::$uagent)); } } } //銷燬 session_destroy() public static function destroy($sid) { $sql = "delete from session where sid=?"; $stmt = self::$pdo->prepare($sql); return $stmt -> execute(array($sid)); } //回收垃圾 public static function gc($maxlifetime) { // utime < ctime - self::$maxlifetime $sql = "delete from session where utime < ?"; $stmt = self::$pdo->prepare($sql); return $stmt -> execute(array(self::$ctime - self::$maxlifetime)); } } //開啓 DBSession::start($pdo);
用數據庫來同步Session會加大數據庫的負擔,由於數據庫原本就是容易產生瓶頸的地方,但若是採用MemCache來處理Session是很是合適的,由於MemCache的緩存機制和Session很是類似。另外,MemCach能夠作分佈式,可以把Web服務器中的內存組合起來,成爲一個」內存池」,不論是哪一個服務器產生的Session,均可以放到這個「內存池」中,其餘的Web服務器均可以使用。以這種方式來同步Session,不會加大數據庫的負擔,而且安全性也要比使用Cookie高。把session放到內存裏面,讀取也要比其餘處理方式快不少
自定義使用memcached處理session信息,和自定義數據庫的處理方式相同,但要簡單得多,由於MemCache的工做機制和Session技術很類似
<?php class MemSession { public static $mem; //pdo的對象 public static $maxlifetime; //最大的生存時間 public static function start(Memcache $mem) { self::$mem = $mem; self::$maxlifetime = ini_get("session.gc_maxlifetime"); //註冊過程, 讓PHP本身處理session時,按照這個函數指定的幾個週期來完成 session_set_save_handler( array(__CLASS__, "open"), array(__CLASS__,"close"), array(__CLASS__, "read"), array(__CLASS__, "write"), array(__CLASS__, "destroy"), array(__CLASS__,"gc")); session_start(); //開啓會話 } // 開啓時,session_start() public static function open($path, $name) { return true; } //關閉 public static function close() { return true; } //讀取 echo $_SESSION['username'] public static function read($sid) { $data = self::$mem -> get($sid); if(empty($data)) { return ''; } return $data; } //寫入 public static function write($sid, $data) { self::$mem -> set($sid, $data, MEMCACHE_COMPRESSED, self::$maxlifetime); } //銷燬 session_destroy() public static function destroy($sid) { self::$mem -> delete($sid, 0); } //回收垃圾 public static function gc($maxlifetime) { return true; } } //建立對象 $mem = new Memcache(); //添加兩臺memcache服務器 $mem -> addServer("localhost", 11211); $mem -> addServer("192.168.1.3", 11211); //開啓 MemSession::start($mem); ?>