### 定制 php4 的 session 功能

这篇文章描述了怎样定制 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 (ying@zippydesign.com)
* 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 <ying@zippydesign.com>.
*
* ------------------------------------------------------------------------
* 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 (ying@zippydesign.com)
* 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 <ying@zippydesign.com>.
*
* ------------------------------------------------------------------------
* 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 (ying@zippydesign.com)
* 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>