一、php中session的生成機制php
session是保存在服務器的,當咱們在代碼中調用session_start();時,PHP會同時往SESSION的存放目錄(默認爲/tmp/)和客戶端的 cookie目錄各生成一個文件。session文件名稱像這樣:mysql
![](http://static.javashuo.com/static/loading.gif)
格式爲sess_{SESSIONID} ,這時session文件中沒有任何內容,當咱們在session_start();並添加了這兩行代碼:
web
$_SESSION['name'] = 'wanchun0222';
$_SESSION['blog'] = 'coderbolg.net';
sql
session文件會生成以下代碼保存在session文件中
name|s:11:"wanchun0222";blog|s:13:"coderbolg.net";
這時再看看cookie:
![](http://static.javashuo.com/static/loading.gif)
能夠看到服務器爲咱們自動生成了一個cookie,cookie名稱爲"PHPSESSID",cookie內容是一串字符,其實這串字符就是 {SESSIONID}。當咱們使用session時,PHP就先生成一個惟一的SESSIONID號(如 2bd170b3f86523f1b1b60b55ffde0f66),再在咱們服務器的默認目錄下生成一個文件,文件名爲 sess_{SESSIONID},同時在當前用戶的客戶端生成一個cookie,內容已經說過了。這樣PHP會爲每個用戶生成一個 SESSIONID,也就是說一個用戶一個session文件。PHP第一次爲某個用戶使用session時就向客戶端寫入了cookie,當這個用戶以 後訪問時,瀏覽器會帶上這個cookie,PHP在拿到cookie後就讀出裏面的SESSIONID,拿着這個SESSIONID去session目錄 下找session文件。找到後在調用$_SESSION['blog']的時候顯示出來。thinkphp
二、php中session的過時回收機制數據庫
咱們明白了session的生成及工做原理,發如今session目錄下會有許多session文件。固然這些文件必定不是永遠存在的,PHP必定 提供了一種過時回收機制。在php.ini中session.gc_maxlifetime爲session設置了生存時間(默認爲1440s)。若是 session文件的最後更新時間到如今超過了生存時間,這個session文件就被認爲是過時的了。在下一次session回收的時候就會被刪除。那下 一次session回收是在何時呢?這和php請求次數有關的。在PHP內部機制中,當php被請求了N次後就會有一次觸發回收機制。究竟是請求多少 次觸發一次是經過如下兩個參數控制的:數組
session.gc_probability = 1
session.gc_divisor = 100
瀏覽器
這是php.ini的默認設置,意思是每100次PHP請求就有一次回收發生。機率是 gc_probability/gc_divisor 。咱們瞭解了服務器端的session過時機制,再來看看客戶端的cookie的過時機制。
若是cookie失效了瀏覽器天然發送不了cookie到服務器,這時即便服務器的session文件存在也沒用,由於PHP不知道要讀取哪一個 session文件。咱們知道PHP的cookie過時時間是在建立時設置的,那麼PHP在建立session的同時爲客戶端建立的cookie的生命周 期是多久呢?這個在php.ini中有設置:session.cookie_lifetime 。這個值默認是0,表明瀏覽器一關閉SESSIONID就失效。那就是說咱們把session.gc_maxlifetime和 session.cookie_lifetime設置成同一個值就能夠控制session的失效時間了。服務器
三、php中session的客戶端存儲機制cookie
由上面的介紹咱們能夠知道,若是用戶關閉了cookie,那咱們的session就徹底無法工做了。是的,確實是這樣。php中session的客 戶端存儲機制只有cookie嗎?不是的。既然咱們的SESSIONID 不能經過cookie傳遞到各個頁面,那咱們還有另外一個法寶,就是經過頁面GET傳值的方式。
PHP能夠在cookie被禁用時自動經過GET方式跨頁傳遞SESSIONID,前提是設置php.ini的 session.use_trans_sid爲1。這時當咱們在客戶端禁用了cookie時使用了session,並在當前頁面經過點擊連接到另外一頁面 時,PHP會自動在連接上添加SESSIONID參數,像這 樣:nextpage.php?SESSIONID=2bd170b3f86523f1b1b60b55ffde0f66。
<?php
/*============================文件說明========================================
@filename: session.class.php
@description: 數據庫保存在線用戶session,實如今線用戶功能!
@notice: session過時時間一個小時,由於咱們的站點是使用cookie(有效時間是1小時)登陸。
所以咱們只記錄用戶登陸的時間,而不是刷新一次更新一次
刪除數據庫中session記錄的動做發生在用戶超時後執行這個文件或正常退出(session_destory)
@database: database:sessions field:sessionid(char32),uid(int10),last_visit(int10)
@author: duanjianbo
@adddate 2008-8-29
=============================================================================
*/
class session {
private $db;
private $lasttime=3600;//超時時間:一個小時
function session(&$db) {
$this->db = &$db;
session_module_name('user'); //session文件保存方式,這個是必須的!除非在Php.ini文件中設置了
session_set_save_handler(
array(&$this, 'open'), //在運行session_start()時執行
array(&$this, 'close'), //在腳本執行完成或調用session_write_close() 或 session_destroy()時被執行,即在全部session操做完後被執行
array(&$this, 'read'), //在運行session_start()時執行,由於在session_start時,會去read當前session數據
array(&$this, 'write'), //此方法在腳本結束和使用session_write_close()強制提交SESSION數據時執行
array(&$this, 'destroy'), //在運行session_destroy()時執行
array(&$this, 'gc') //執行機率由session.gc_probability 和 session.gc_divisor的值決定,時機是在open,read以後,session_start會相繼執行open,read和gc
);
session_start(); //這也是必須的,打開session,必須在session_set_save_handler後面執行
}
function unserializes($data_value) {
$vars = preg_split(
'/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\|/',
$data_value, -1, PREG_SPLIT_NO_EMPTY |
PREG_SPLIT_DELIM_CAPTURE
);
for ($i = 0; isset($vars[$i]); $i++) {
$result[$vars[$i++]] = unserialize($vars[$i]);
}
return $result;
}
function open($path, $name) {
return true;
}
function close() {
$this->gc($this->lasttime);
return true;
}
function read($SessionKey){
$sql = "SELECT uid FROM sessions WHERE session_id = '".$SessionKey."' limit 1";
$query =$this->db->query($sql);
if($row=$this->db->fetch_array($query)){
return $row['uid'];
}else{
return "";
}
}
function write($SessionKey,$VArray) {
require_once(MRoot.DIR_WS_CLASSES .'db_mysql_class.php');
$db1=new DbCom();
// make a connection to the database... now
$db1->connect(DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD, DB_DATABASE);
$db1->query("set names utf8");
$this->db=$db1;
$SessionArray = addslashes($VArray);
$data=$this->unserializes($VArray);
$sql0 = "SELECT uid FROM sessions WHERE session_id = '".$SessionKey."' limit 1";
$query0 =$this->db->query($sql0);
if($this->db->num_rows($query0)<=0){
if (isset($data['webid']) && !empty($data['webid'])) {
$this->db->query("insert into `sessions` set `session_id` = '$SessionKey',uid='".$data['webid']."',last_visit='".time()."'");
}
return true;
}else{
/*$sql = "update `sessions` set ";
if(isset($data['webid'])){
$sql .= "uid = '".$data['webid']."', " ;
}
$sql.="`last_visit` = null "
. "where `session_id` = '$SessionKey'";
$this->db->query($sql); */
return true;
}
}
function destroy($SessionKey) {
$this->db->query("delete from `sessions` where `session_id` = '$SessionKey'");
return true;
}
function gc($lifetime) {
$this->db->query("delete from `sessions` where unix_timestamp(now()) -`last_visit` > '".$this->lasttime."'");
return true;
}
}
?>
以上代碼放入到文件session_db.php中,
而後每個文件頭最上方引用一下include_once('session_db.php')便可。
另外,在php.ini中要將session.save_handler設置爲"user"便可。
這樣就能夠將數據庫保存到mysql的tb_session表中,能夠有不少人訪問,不再用擔憂訪問的人過多致使session_temp文件目錄中的碎文件過多讀取效率低下的問題了。
隨輔:tb_session的數據庫腳本:
CREATE TABLE `tb_session` (
`session_key` varchar(30) NOT NULL,
`session_data` varchar(60) DEFAULT NULL,
`session_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`session_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
如下同上代碼做用一致,代碼稍做調整
<?php
/*
*@author Fahy
*數據庫爲mysql,
*數據庫名爲session,表名爲session,
*表中字段包括PHPSESSID,update_time,client_ip,data
*/
class Session{
private static $handler = null;
private static $ip = null;
private static $lifetime = null;
private static $time = null;
//配置靜態變量
private static function init($handler){
self::$handler = $handler; //獲取數據庫資源
self::$ip = !empty($_SERVER["REMOTE_ADDR"])? $_SERVER["REMOTE_ADDR"]:'unkonw'; //獲取客戶端ip
self::$lifetime = ini_get('session.gc_maxlifetime'); //獲取session生命週期
self::$time = time(); //獲取當前時間
}
//調用session_set_save_handler()函數並開啓session
static function start($pdo){
self::init($pdo);
session_set_save_handler(
array(__CLASS__,'open'),
array(__CLASS__,'close'),
array(__CLASS__,'read'),
array(__CLASS__,'write'),
array(__CLASS__,'destroy'),
array(__CLASS__,'gc')
);
session_start();
}
public static function open($path,$name){
return true;
}
public static function close(){
return true;
}
//查詢數據庫中的數據
public static function read($PHPSESSID){
$sql = "select PHPSESSID,update_time,client_ip,data from session where PHPSESSID=?";
$stmt = self::$handler->prepare($sql);
$stmt->execute(array($PHPSESSID));
if(!$result = $stmt->fetch(PDO::FETCH_ASSOC)){
return '';
}
if(self::$ip == $result['client_ip']){
self::destroy($PHPSESSID);
return '';
}
if(($result['update_time']+self::$lifetime)<self::$time){
self::destroy($PHPSESSID);
return '';
}
return $result['data'];
}
/*
*首先查詢該session是否存在數據,若是存在,則更新數據,若是不存在,則插入數據
*/
//將session寫入數據庫中,$data傳入session中的keys和values數組
public static function write($PHPSESSID,$data){
$sql = "select PHPSESSID,update_time,client_ip,data from session where PHPSESSID=?";
$stmt = self::$handler->prepare($sql);
$stmt->execute(array($PHPSESSID));
if($result=$stmt->fetch(PDO::FETCH_ASSOC)){
if($result['data'] != $data || self::$time > ($result['update_time']+30)){
$sql = "update session set update_time=?,data=? where PHPSESSID = ?";
$stmt = self::$handler->prepare($sql);
$stmt->execute(array($self::$time,$data,$PHPSESSID));
}
}else{
if(!empty($data)){
try{
$sql = "insert into session(PHPSESSID,update_time,client_ip,data) values(?,?,?,?)";
}catch(PDOException $e){
echo $e->getMessage();
}
$sth = self::$handler->prepare($sql);
$sth->execute(array($PHPSESSID,self::$time,self::$ip,$data));
}
}
return true;
}
public static function destroy($PHPSESSID){
$sql = "delete from session where PHPSESSID = ?";
$stmt = self::$handler->prepare($sql);
$stmt->execute(array($PHPSESSID));
return true;
}
public static function gc($lifetime){
$sql = "delete from session where update_time<?";
$stmt = self::$handler->prepare($sql);
$stmt->execute(array(self::$time-$lifetime));
return true;
}
}
//使用PDO鏈接數據庫
try{
$pdo = new PDO("mysql:host=localhost;dbname=session","root","hwj193");
}catch(PDOException $e){
echo $e->getMessage();
}
//傳遞數據庫資源
Session::start($pdo);
<?php
try{
$pdo = new PDO('mysql:host=localhost;dbname=rphp4zf', 'root','');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
new SessionToDB($pdo);
} catch(PDOException $e) {
echo 'Error: '.$e->getMessage();
}
class SessionToDB
{
private $_path = null;
private $_name = null;
private $_pdo = null;
private $_ip = null;
private $_maxLifeTime = 0;
public function __construct(PDO $pdo)
{
session_set_save_handler(
array(&$this, 'open'),
array(&$this, 'close'),
array(&$this, 'read'),
array(&$this, 'write'),
array(&$this, 'destroy'),
array(&$this, 'gc')
);
$this->_pdo = $pdo;
$this->_ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
$this->_maxLifeTime = ini_get('session.gc_maxlifetime');
}
public function open($path,$name)
{
return true;
}
public function close()
{
return true;
}
public function read($id)
{
$sql = 'SELECT * FROM session where PHPSESSID = ?';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array($id));
if (!$result = $stmt->fetch(PDO::FETCH_ASSOC)) {
return null;
} elseif ($this->_ip != $result['client_ip']) {
return null;
} elseif ($result['update_time']+$this->_maxLifeTime < time()){
$this->destroy($id);
return null;
} else {
return $result['data'];
}
}
public function write($id,$data)
{
$sql = 'SELECT * FROM session where PHPSESSID = ?';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array($id));
if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
if ($result['data'] != $data) {
$sql = 'UPDATE session SET update_time =? , date = ? WHERE PHPSESSID = ?';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array(time(), $data, $id));
}
} else {
if (!empty($data)) {
$sql = 'INSERT INTO session (PHPSESSID, update_time, client_ip, data) VALUES (?,?,?,?)';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array($id, time(), $this->_ip, $data));
}
}
return true;
}
public function destroy($id)
{
$sql = 'DELETE FROM session WHERE PHPSESSID = ?';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array($id));
return true;
}
public function gc($maxLifeTime)
{
$sql = 'DELETE FROM session WHERE update_time < ?';
$stmt = $this->_pdo->prepare($sql);
$stmt->execute(array(time() - $maxLifeTime));
return true;
}
}