這篇文章描述了怎樣定制php4的session處理。我們提供一個怎樣寫一個全功能的基於mysql數據庫或dbm文件的session處理程序例子。
一、序言
新的php4有一套自己的session處理函數。缺省情況下,每個session存貯在系統臨時目錄的一個個獨立文件中(例如在unix系統中為/tmp)。
這適合或不適合,依你的需求而言。例如:如果你的支持php的web服務器分布在不同的機器上,你不能很容易地共享它們之間的session(當然,你也可以將sessions保存在NFS共享中)。另一個潛在的問題是你機器上的數千或數百萬個session文件使你的文件系統變得散亂 。
對我們來說幸運的是,php4的開發者非常有遠見(感謝他們),他們為你我這樣的用戶提供了擴展session處理的接口。
這個文檔解釋一點session的處理並且提供兩個能夠工作的怎樣擴展session處理的例子。我們的第一個例子將使session處理程序保存 session數據到DBM文件中。我們的第二個例子將保存session數據到MYSQL數據庫中。
在你開始之前,請下載ying20000602.zip 並且將它解開放到web文檔目錄中。(我已經將它帶在本文的結尾處了)
任何一個我們寫的session處理程序會提供6個基本的函數,它們將被php4的session處理程序調用,所以你不用擔心怎樣調用它們。
好在這些定制處理session的函數對你來說是完全透明的。所以你可以改動它們而不會影響你自己的PHP腳本。
這幾個函數是:
sess_open($sess_path, $session_name);
這個函數被session處理程序調用來作初始化工作。需要傳給它的兩個參數是$sess_path,它對應你的php.ini文件中的session.save_path選項;$session_name,它對應php.ini中的session.name選項。它們具體怎樣工作,請看下面的例子。
sess_close();
這個函數在頁面結束執行並且session處理程序需要關閉時被調用。(注意,不要和sess_destory混淆了,它是用來結束session的)
sess_read($key);
這個函數在session處理程序讀取指定session鍵值($key)時。
這個函數檢索並返回標識為$key的session數據.(注意:你不用擔心怎樣序列化和反序列化數據,如果你不知道這是什麼意思,不要擔心它)
譯者注:序列化是將變量或對象在程序結束或需要時保存在文件中,在下次程序運行或需要時再調入內存的技術,有別於只保存數據的方法。
sess_write($key, $val);
這個函數據在session處理程序需要將數據保存時調用,這種情況經常在你的程序結束時發生。
它負責將數據保存在下次能用sess_read($key)函數檢索的地方。
sess_destroy($key);
這個函數在需要消毀session時。它負責刪除session並且清除環境。
sess_gc($maxlifetime);
這個函數負責清理碎片。在這種情況下,它負責刪除過時的session數據。session處理程序會偶爾調用它們。
現在我們已經清楚了我們提供的函數。它們不是非要這樣命名,但必須接受這些參數。(不管你需不需要它們)
DBM session 處理程序
我們的第一個范例是寫一個保存session數據到DBM文件中的定制session處理程序。(這是ying20000602.zip中的session_dbm.php文件)
有很多充足的理由讓你要這樣做,例如,如果你在isp那兒有一台共享的服務器(譯注:相當於我們說的虛擬主機吧)並且你不想讓你的session數據
和別人的混在一起。
重要注釋:
在你試驗這些程序時你的php4必須有DBM支持。如果不是這樣的(譯注:如果沒有DBM支持)會很難看,真的很難看!
我們要做的這些工作將會得到一個所有session數據的DBM文件。(萬一你不知道,DBM文件象一個僅保存"鍵/值"對的非常簡單的數據庫.
由下面的6個函數據實現:
sess_open($sess_path, $session_name);
我們將調用dbmopen()打開一個處於讀寫模式的DBM文件。我們的DBM文件將被命名為/tmp/PHPSESSID,除非你修改了php.ini中的session路
徑和名字設置。
sess_close();
在這個函數中,我們將簡單地調用dbmclose()函數關閉DBM文件。
sess_read($key);
這兒我們僅僅調用dbmfetch()載入和參數$key相關連的session數據。
在載入一個session時,我們需要保證讀入的不是一個過期數據,所以我們必須給session配上一個時間標記。
為什麼?因為在它們失效,不管什麼原因而沒有被刪掉時,我們不會意外地讀入過期數據。這會是一個很大的禁忌。
我們知道DBM文件只保存 鍵/值 對,因此不得不在寫session數據時將時間標記同" 值"一起寫入,在讀session數據時去掉。
任何已經過期的session將被忽略。看看這個源程序,它會讓你更清楚。
sess_write($key, $val);
寫入一個session,我們會使用dbmreplace()函數。注意,從上所述我們要保存過期時間標記在session中,所以我們要將時間標記綁到值上。
sess_destroy($key);
消毀一個session很容易,我們只需要調用dbmdelete()函數將它從session文件中刪除。
sess_gc($maxlifetime);
過期數據收集在這兒有點令人討厭但卻是必需的,為了達到目的我們在循環掃描所有保存在DBM文件中的session並且刪掉過期的。這會很慢因
為我們循環通過所有保存在這個文件中的所有session數據。
現在我們已經有了一個DBM session處理程序,太酷了!
現在,我們讓這些session保存到mysql數據庫中。
Mysql session處理程序
(This
我們的下一個范例是寫一個將session數據存到mysql數據庫的定制session處理程序。(這個在session_mysql.php文件中,見文章尾部)
在你有許多支持PHP的服務器並且你需要共享它們之間的session時你會想將session保存在數據庫中的。(比如你服務於很多用戶並且需要
負載平衡時)
You have a bunch of machines doing web/PHP stuff, a machine serving
你有一批機器作支持php的服務器,需要一台機器作你的普通數據庫服務器,另外一台運行mysql數據庫處理session。僅這樣對大多數人來
說就具有很大的殺傷力的。:)(譯注:可能意思是太酷了吧)
重要提示:
在你試驗之前你的php必須支持mysql。(譯注:這好象已經不成問題了,php現在已經內建mysql支持了)如果不是這樣的話,事情會很難看,真的
很難看。
首先我們在mysql中創建一個session數據庫,並且創建一個session表。先運行你的mysql客戶端並且運行下面的命令:
mysql> CREATE DATABASE sessions;
mysql> GRANT select, insert, update, delete ON sessions.* TO phpsession@localhost
-> IDENTIFIED BY 'phpsession';
mysql> CREATE TABLE sessions (
-> sesskey char(32) not null,
-> expiry int(11) unsigned not null,
-> value text not null,
-> PRIMARY KEY (sesskey)
-> );
下一步,修改session_mysql.php文件的$SESS_DB* 變量使其匹配你機器上的數據庫設置。在你繼續之前確信一切看起來良好。
我們的6個函數會依靠mysql數據庫工作:
sess_open($sess_path, $session_name);
我們需要調用mysql_Pconnect(),然後用mysql_selsect_db()選擇session數據庫 。$sess_path 和$session_name 參數
是無關的但我們不得不保留它們。(譯注:原文如此,可能是為了兼容吧)
sess_close();
我們要打開一個mysql永久連接因此我們在這個函數中不做任何事。(一個空函數)
sess_read($key);
這個竅門就是一個簡單的select語句,我們想要讀取所給的$key的session數據,需要指定過期時間信息。
sess_write($key, $val);
寫session數據用了一個小把戲。我們首先試圖用insert語句保存session數據到數據庫中。如果失敗(主鍵約束)則意味著這個key已經寫入,然後我們
不得不用一update語句代替。
sess_destroy($key);
刪除一個session很容易,我們只需要從數據庫中刪除這個鍵值。
sess_gc($maxlifetime);
處理過期session也很容易,我們只需要從數據庫中刪除過期session().
作為結束這個小教程,希望你在擴展php4的session處理時有個好感覺。
這個范例只是簡單的示范了你怎樣做才能擴展它們使其適應你的需要,如果你找到什麼bug的話,請讓我知道:)
faq:
如果你擔心session文件在/tmp目錄中和別的虛擬機混淆,正好將它們存到別處。
這就是為什麼有session.save_path選項的原因。
如果你擔心性能的話你可以考慮將session存在共享內存中。你只需要在編譯php時加上MM支持(--with-mm)並指定sessio.save_handler 為 mm
在.htaccess中,php.ini中或httpd.conf中。
我覺得只有多台機器要保存session時才會用到數據庫。
(最後這句實在不好譯,自已看吧)
session_dbm.php
=========================================================================
<?
/* ------------------------------------------------------------------------
* session_dbm.php
* ------------------------------------------------------------------------
* PHP4 DBM Session Handler
* Version 1.00
* by Ying Zhang ([email protected])
* Last Modified: May 21 2000
*
* ------------------------------------------------------------------------
* TERMS OF USAGE:
* ------------------------------------------------------------------------
* You are free to use this library in any way you want, no warranties are
* expressed or implied. This works for me, but I don't guarantee that it
* works for you, USE AT YOUR OWN RISK.
*
* While not required to do so, I would appreciate it if you would retain
* this header information. If you make any modifications or improvements,
* please send them via email to Ying Zhang <[email protected]>.
*
* ------------------------------------------------------------------------
* DESCRIPTION:
* ------------------------------------------------------------------------
* This library tells the PHP4 session handler to write to a DBM file
* instead of creating individual files for each session.
*
* ------------------------------------------------------------------------
* INSTALLATION:
* ------------------------------------------------------------------------
* Make sure you have DBM support compiled into PHP4. Then copy this
* script to a directory that is accessible by the rest of your PHP
* scripts.
*
* ------------------------------------------------------------------------
* USAGE:
* ------------------------------------------------------------------------
* Include this file in your scripts before you call session_start(), you
* don't have to do anything special after that.
*/
$SESS_DBM = "";
$SESS_LIFE = get_cfg_var("session.gc_maxlifetime");
function sess_open($save_path, $session_name) {
global $SESS_DBM;
$SESS_DBM = dbmopen("$save_path/$session_name", "c");
return ($SESS_DBM);
}
function sess_close() {
global $SESS_DBM;
dbmclose($SESS_DBM);
return true;
}
function sess_read($key) {
global $SESS_DBM, $SESS_LIFE;
$var = "";
if ($tmp = dbmfetch($SESS_DBM, $key)) {
$expires_at = substr($tmp, 0, strpos($tmp, "|"));
if ($expires_at > time()) {
$var = substr($tmp, strpos($tmp, "|") + 1);
}
}
return $var;
}
function sess_write($key, $val) {
global $SESS_DBM, $SESS_LIFE;
dbmreplace($SESS_DBM, $key, time() + $SESS_LIFE . "|" . $val);
return true;
}
function sess_destroy($key) {
global $SESS_DBM;
dbmdelete($SESS_DBM, $key);
return true;
}
function sess_gc($maxlifetime) {
global $SESS_DBM;
$now = time();
$key = dbmfirstkey($SESS_DBM);
while ($key) {
if ($tmp = dbmfetch($SESS_DBM, $key)) {
$expires_at = substr($tmp, 0, strpos($tmp, "|"));
if ($now > $expires_at) {
sess_destroy($key);
}
}
$key = dbmnextkey($SESS_DBM, $key);
}
}
session_set_save_handler(
"sess_open",
"sess_close",
"sess_read",
"sess_write",
"sess_destroy",
"sess_gc");
?>
=======================================
session_mysql.php
=======================================
<?
/* ------------------------------------------------------------------------
* session_mysql.php
* ------------------------------------------------------------------------
* PHP4 MySQL Session Handler
* Version 1.00
* by Ying Zhang ([email protected])
* Last Modified: May 21 2000
*
* ------------------------------------------------------------------------
* TERMS OF USAGE:
* ------------------------------------------------------------------------
* You are free to use this library in any way you want, no warranties are
* expressed or implied. This works for me, but I don't guarantee that it
* works for you, USE AT YOUR OWN RISK.
*
* While not required to do so, I would appreciate it if you would retain
* this header information. If you make any modifications or improvements,
* please send them via email to Ying Zhang <[email protected]>.
*
* ------------------------------------------------------------------------
* DESCRIPTION:
* ------------------------------------------------------------------------
* This library tells the PHP4 session handler to write to a MySQL database
* instead of creating individual files for each session.
*
* Create a new database in MySQL called "sessions" like so:
*
* CREATE TABLE sessions (
* sesskey char(32) not null,
* expiry int(11) unsigned not null,
* value text not null,
* PRIMARY KEY (sesskey)
* );
*
* ------------------------------------------------------------------------
* INSTALLATION:
* ------------------------------------------------------------------------
* Make sure you have MySQL support compiled into PHP4. Then copy this
* script to a directory that is accessible by the rest of your PHP
* scripts.
*
* ------------------------------------------------------------------------
* USAGE:
* ------------------------------------------------------------------------
* Include this file in your scripts before you call session_start(), you
* don't have to do anything special after that.
*/
$SESS_DBHOST = "localhost"; /* database server hostname */
$SESS_DBNAME = "sessions"; /* database name */
$SESS_DBUSER = "phpsession"; /* database user */
$SESS_DBPASS = "phpsession"; /* database password */
$SESS_DBH = "";
$SESS_LIFE = get_cfg_var("session.gc_maxlifetime");
function sess_open($save_path, $session_name) {
global $SESS_DBHOST, $SESS_DBNAME, $SESS_DBUSER, $SESS_DBPASS, $SESS_DBH;
if (! $SESS_DBH = mysql_pconnect($SESS_DBHOST, $SESS_DBUSER, $SESS_DBPASS)) {
echo "<li>Can't connect to $SESS_DBHOST as $SESS_DBUSER";
echo "<li>MySQL Error: ", mysql_error();
die;
}
if (! mysql_select_db($SESS_DBNAME, $SESS_DBH)) {
echo "<li>Unable to select database $SESS_DBNAME";
die;
}
return true;
}
function sess_close() {
return true;
}
function sess_read($key) {
global $SESS_DBH, $SESS_LIFE;
$qry = "SELECT value FROM sessions WHERE sesskey = '$key' AND expiry > " . time();
$qid = mysql_query($qry, $SESS_DBH);
if (list($value) = mysql_fetch_row($qid)) {
return $value;
}
return false;
}
function sess_write($key, $val) {
global $SESS_DBH, $SESS_LIFE;
$expiry = time() + $SESS_LIFE;
$value = addslashes($val);
$qry = "INSERT INTO sessions VALUES ('$key', $expiry, '$value')";
$qid = mysql_query($qry, $SESS_DBH);
if (! $qid) {
$qry = "UPDATE sessions SET expiry = $expiry, value = '$value' WHERE sesskey = '$key' AND expiry > " . time();
$qid = mysql_query($qry, $SESS_DBH);
}
return $qid;
}
function sess_destroy($key) {
global $SESS_DBH;
$qry = "DELETE FROM sessions WHERE sesskey = '$key'";
$qid = mysql_query($qry, $SESS_DBH);
return $qid;
}
function sess_gc($maxlifetime) {
global $SESS_DBH;
$qry = "DELETE FROM sessions WHERE expiry < " . time();
$qid = mysql_query($qry, $SESS_DBH);
return mysql_affected_rows($SESS_DBH);
}
session_set_save_handler(
"sess_open",
"sess_close",
"sess_read",
"sess_write",
"sess_destroy",
"sess_gc");
?>
=========================================================================
test.php
==========================================================================
<?
/* ------------------------------------------------------------------------
* test.php
* ------------------------------------------------------------------------
* PHP4 Customer Session Handler Test Script
* Version 1.00
* by Ying Zhang ([email protected])
* Last Modified: May 21 2000
*/
/* default to DBM handler */
if (! isset($handler)) {
$handler = "dbm";
}
/* default action is increment */
if (! isset($action)) {
$action = "increment";
}
/* load up the appropriate session handling script, depending on the handler */
if ($handler == "dbm") {
include("session_dbm.php");
} elseif ($handler == "mysql") {
include("session_mysql.php");
} else {
echo "<li>Unrecognized handler ($handler)";
die;
}
/* start the session and register a simple counter */
session_start();
session_register("count");
/* figure out what we should do, depending on the action */
switch ($action) {
case "increment" :
$count = isset($count) ? $count + 1 : 0;
break;
case "destroy" :
session_destroy();
break;
case "gc" :
$maxlife = get_cfg_var("session.gc_maxlifetime");
sess_gc($maxlife);
break;
default:
echo "<li>Unknown action ($action)";
break;
}
?>
<h1>Session Test Script</h1>
<ul>
<li>Handler: <b><?=$handler?></b>
<li>Action: <b><?=$action?></b>
<li>Count: <b><?=$count?></b>
</ul>
<hr size=1>
<form>
<table>
<tr>
<td>Handler:</td>
<td>
<select name="handler">
<option value="dbm">DBM</option>
<option value="mysql">MySQL</option>
</select>
</td>
</tr>
<tr>
<td>Action:</td>
<td>
<select name="action">
<option value="increment">Increment</option>
<option value="destroy">Session Destroy</option>
<option value="gc">Force Garbage Collection</option>
</select>
</td>
</tr>
<tr>
<td></td>
<td><br><input type="submit"></td>
</tr>
</table>
</form>