php默认把会话保存在临时文件中,保存在数据库中可以提高安全性,在共享主机服务器上,所有web站点都使用同一个临时目录,这意味着数十个程序都在同一位置进行文件读取的操作,我们很容易就编写一个脚本从这个目录里读取全部文件的数据。(在共享主机,涉及安全的另一个技巧是改变会话目录,session_save_path)。
另外,把会话数据保存在数据库还有利于更方便检索web站点会话的更多信息,我们可以查询活动会话的数量,还可以对会话数据进行备份。
把会话数据保存在数据库的第3个原因是,如果站点运行在多个服务器,同一个用于在一个会话过程中可能对不同服务器上的多个页面产生请求,而会话数据如果保存在某台服务器上的文件里,就不能被其他服务器上的页面所使用。这不是大多数开发者可能会面临的状态,但如果遇到这种情况,除了使用数据库保存会话数据之外没有其他的解决办法。
1.创建sessions表。
sessions表至少要有3个字段:
id为会话id,data为会话数据,last_accessed为会话数据最后一次访问时间。
2.定义会话函数,让php使用这些函数。
bool session_set_save_handler ( callable $open
, callable $close
, callable $read
,callable $write
, callable $destroy
, callable $gc
)
open(string $savePath, string $sessionName)
The open callback works like a constructor in classes and is executed when the session is being opened. It is the first callback function executed when the session is started automatically or manually with session_start(). Return value is TRUE
for success, FALSE
for failure.
close()
The close callback works like a destructor in classes and is executed after the session write callback has been called. It is also invoked when session_write_close() is called. Return value should be TRUE
for success, FALSE
for failure.
read(string $sessionId)
The read
callback must always return a session encoded (serialized) string, or an empty string if there is no data to read.
This callback is called internally by PHP when the session starts or when session_start() is called. (session_start()执行了调用了这个read函数)Before this callback is invoked PHP will invoke the open
callback.
The value this callback returns must be in exactly the same serialized format that was originally passed for storage to the write
callback. The value returned will be unserialized automatically by PHP and used to populate the$_SESSION superglobal. While the data looks similar to serialize() please note it is a different format which is speficied in the session.serialize_handler ini setting.
write(string $sessionId, string $data)
The write
callback is called when the session needs to be saved and closed. This callback receives the current session ID a serialized version the $_SESSION superglobal. The serialization method used internally by PHP is specified in the session.serialize_handler ini setting.
The serialized session data passed to this callback should be stored against the passed session ID. When retrieving this data, the read
callback must return the exact value that was originally passed to the write
callback.
This callback is invoked when PHP shuts down or explicitly when session_write_close() is called. Note that after executing this function PHP will internally execute the close
callback.
Note:
The "write" handler is not executed until after the output stream is closed. Thus, output from debugging statements in the "write" handler will never be seen in the browser. If debugging output is necessary, it is suggested that the debug output be written to a file instead.
destroy($sessionId)
This callback is executed when a session is destroyed with session_destroy() or with session_regenerate_id() with the destroy parameter set to TRUE
. Return value should be TRUE
for success, FALSE
for failure.
gc($lifetime)
The garbage collector callback is invoked internally by PHP periodically in order to purge old session data. The frequency is controlled by session.gc_probability and session.gc_divisor. The value of lifetime which is passed to this callback can be set in session.gc_maxlifetime. Return value should be TRUE
for success, FALSE
for failure.
过程式脚本:
<?php # Script 3.1 - db_sessions.inc.php /*
* This page creates the functional
* interface for storing session data
* in a database.
* This page also starts the session.
*/ // Global variable used for the database
// connections in all session functions:
$sdbc = NULL; // Define the open_session() function:
// This function takes no arguments.
// This function should open the database connection.
function open_session() { global $sdbc; // Connect to the database.
$sdbc = mysqli_connect ('localhost', 'root', 'sm159357', 'yii_blog') OR die ('Cannot connect to the database.'); return true; } // End of open_session() function. // Define the close_session() function:
// This function takes no arguments.
// This function closes the database connection.
function close_session() { global $sdbc; return mysqli_close($sdbc); } // End of close_session() function. // Define the read_session() function:
// This function takes one argument: the session ID.
// This function retrieves the session data.
function read_session($sid) { global $sdbc; // Query the database:
$q = sprintf('SELECT data FROM sessions WHERE id="%s"', mysqli_real_escape_string($sdbc, $sid));
$r = mysqli_query($sdbc, $q); // Retrieve the results:
if (mysqli_num_rows($r) == 1) { list($data) = mysqli_fetch_array($r, MYSQLI_NUM); // Return the data:
return $data; } else { // Return an empty string.
return '';
} } // End of read_session() function. // Define the write_session() function:
// This function takes two arguments:
// the session ID and the session data.
function write_session($sid, $data) { global $sdbc; // Store in the database:
$q = sprintf('REPLACE INTO sessions (id, data) VALUES ("%s", "%s")', mysqli_real_escape_string($sdbc, $sid), mysqli_real_escape_string($sdbc, $data));
$r = mysqli_query($sdbc, $q); return mysqli_affected_rows($sdbc); } // End of write_session() function. // Define the destroy_session() function:
// This function takes one argument: the session ID.
function destroy_session($sid) { global $sdbc; // Delete from the database:
$q = sprintf('DELETE FROM sessions WHERE id="%s"', mysqli_real_escape_string($sdbc, $sid));
$r = mysqli_query($sdbc, $q); // Clear the $_SESSION array:
$_SESSION = array(); return mysqli_affected_rows($sdbc); } // End of destroy_session() function. // Define the clean_session() function:
// This function takes one argument: a value in seconds.
function clean_session($expire) { global $sdbc; // Delete old sessions:
$q = sprintf('DELETE FROM sessions WHERE DATE_ADD(last_accessed, INTERVAL %d SECOND) < NOW()', (int) $expire);
$r = mysqli_query($sdbc, $q); return mysqli_affected_rows($sdbc); } // End of clean_session() function. # **************************** #
# ***** END OF FUNCTIONS ***** #
# **************************** # // Declare the functions to use:
session_set_save_handler('open_session', 'close_session', 'read_session', 'write_session', 'destroy_session', 'clean_session'); // Make whatever other changes to the session settings. // Start the session:
session_start(); ?>
注意write_session的sql用到了replace into;
作用是没有就insert,有就update。
clean_session就是gc垃圾收集,参数$expire, DATE_ADD(last_accessed,INTERVAL $expire SECOND<NOW();接收以秒为单位的时间,用以决定什么是“旧”会话。删除查过指定时间没有被访问过的会话。
在session_start()之前必须调用session_set_save_handler。
使用新会话处理程序
众所周知,php在脚本呢运行时自动关闭数据库连接,对于结下了的范例脚本,这意味着在脚本运行之后,数据库连接是自动关闭的,然后会话函数会尝试向数据库写入数据并关闭连接,这样会导致产生一些莫名其妙的错误(比如“我的数据库连接去哪儿了?”),为了避免这一系列问题,我们应该在脚本结束之前调用session_write_close()函数,他会调用“写入”和“关闭”函数,而这时数据库连接还是有效的。
<?php # Script 3.2 - sessions.php /* This page does some silly things with sessions.
* It includes the db_sessions.inc.php script
* so that the session data will be stored in
* a database.
*/ // Include the sessions file:
// The file already starts the session.
require_once('db_sessions.inc.php');
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<title>DB Session Test</title>
</head>
<body>
<?php // Store some dummy data in the session,
// if no data is present.
if (empty($_SESSION)) { $_SESSION['blah'] = 'umlaut';
$_SESSION['this'] = 3615684.45;
$_SESSION['that'] = 'blue'; // Print a message indicating what's going on:
echo '<p>Session data stored.</p>'; } else { // Print the already-stored data.
echo '<p>Session Data Exists:<pre>' . print_r($_SESSION, 1) . '</pre></p>';
} // Log the user out, if applicable:
if (isset($_GET['logout'])) { session_destroy();
echo '<p>Session destroyed.</p>'; } else { // Print the "Log Out" link:
echo '<a href="sessions.php?logout=true">Log Out</a>';
} // Print out the session data:
echo '<p>Session Data:<pre>' . print_r($_SESSION, 1) . '</pre></p>'; ?>
</body>
</html>
<?php session_write_close(); ?>
不加session_write_close()会报错。
------------------------------------------
我前面写过一篇关于PHP SESSION NAME与SESSION ID关系的文章《SESSION NAME引用SESSION ID的问题(Discuz! 用户(自动)登录原理)》,
有兴趣的朋友可以看一下。我在里面提了一下SESSION在服务器端的存储方式问题,这里我们就使用session_set_save_handler 来实现
SESSION在服务器端的“另存为”。(关于session_set_save_handler的用法参看官网)
我们首先写一个存储类(注意这里没有对DB操作进行封装),暂叫session.class.php:
<?php
class session_mysql
{
private $_db_link;
private $_table;
//这个是SESSION的回收周期(秒)
private $_gc_lifetime = 30;
public function __construct($host, $user, $pwd, $db, $table = 'session', $sessionName = '', $cookieLife = 0)
{
$this->_db_link = mysql_connect ( $host, $user, $pwd );
if (! $this->_db_link)
{
return False;
}
if (mysql_query ( "USE $db" ))
{
$this->_table = $table;
session_set_save_handler ( array (&$this, 'open' ), array (&$this, 'close' ), array (&$this, 'read' ), array (&$this, 'write' ), array (&$this, 'destroy' ), array (&$this, 'gc' ) ); //这个函数里面每个参数都是字符串类型
//周期
$cookieLife = intval ( $cookieLife );
if ($cookieLife > 0)
{
session_set_cookie_params ( $cookieLife );
}
if ($this->_gc_lifetime > 0)
{
ini_set ( 'session.gc_maxlifetime', $this->_gc_lifetime );
} else
{
$this->_gc_lifetime = ini_get ( 'session.gc_maxlifetime' );
}
//名称
if (! empty ( $sessionName ))
{
ini_set ( 'session.name', $sessionName );
}
return session_start ();
}
return False;
}
public function open($save_path, $session_name)
{
return true;
}
public function close()
{
//删除过期的SESSION
$this->gc ( $this->_gc_lifetime );
//关闭数据库
//(注意如果系统其它功能和SESSION共用一个数据库,此处关闭可能会影响到其它功能,根据实际情况而定)
return mysql_close ( $this->_db_link );
}
public function read($id)
{
$id = mysql_real_escape_string ( $id );
$sql = "select `data` from `$this->_table` where `id`='$id' limit 1";
if ($result = mysql_fetch_assoc ( mysql_query ( $sql, $this->_db_link ) ))
{
return $result ['data'];
}
return '';
}
public function write($id, $data)
{
$time = time ();
$id = mysql_real_escape_string ( $id );
$data = mysql_real_escape_string ( $data );
$sql = "replace into `$this->_table` values('$id','$data',$time)";
return mysql_query ( $sql, $this->_db_link );
}
public function destroy($id)
{
$sql = "delete from `$this->_table` where `id`='$id' limit 1";
return mysql_query ( $sql, $this->_db_link );
}
public function gc($lifetime)
{
$expire = time () - $lifetime;
$sql = "delete from `$this->_table where `created_time` < $expire";
return mysql_query ( $sql, $this->_db_link );
}
}
接着我们写一个公用的方法,暂叫function.php:
<?php
function my_session_start($sessionName = '', $lifetime = 0)
{
//这里装载SESSION类(由于这里方便演示,文件结构分布不严谨)
require_once './session.class.php';
//配置好相关内容
$session = new session_mysql ( '127.0.0.1', 'xxx', 'xxx', 'test', 'session', $sessionName, $lifetime );
if ($session)
{
return True;
}
return False;
}
最后,我们来测试一下效果,test.php:
<?php
//测试一下
require_once './function.php';
error_reporting ( E_ALL );
my_session_start ();
$_SESSION ['test'] = 'test';
$_SESSION ['testtwo'] = array ('testtwo' );
我们再在另外一个页面输出SESSION,test2.php
<?php
require_once './function.php';
error_reporting ( E_ALL );
my_session_start ();
var_dump($_SESSION);
运行后我们可以看到:
array 'test' =>string'test'(length=4) 'testtwo' =>array 0 =>string'testtwo'(length=7)
恩,一切正常。我们再看一下数据库中是什么:
恩,浏览器里又是怎么样的呐(这里只测试了FIREFOX,在生产时请务必各浏览器都要测试一下)?
恩,好了以就是一个简单的例子,那么如果我们要使用特殊的SESSION NAME或者要求有过期时间呐?test.php
<?php
//测试一下特殊名称
require_once './function.php';
error_reporting ( E_ALL );
my_session_start ( 'testhehe', 60 );//一分钟
$_SESSION ['lifettimetest'] = 'ok';
恩,我们来获取看看:test2.php
<?php
//测试一下特殊名称
require_once './function.php';
error_reporting ( E_ALL );
my_session_start ( 'testhehe' );
var_dump ( $_SESSION );
恩,我们来输出:
array 'lifettimetest' =>string'ok'(length=2)恩,貌似正常,我们再看看数据库与浏览器里的值:(数据库)
浏览器:
这样看来我们测试成功了。好了,以上就是关于session_set_save_handler的操作方法了。恩,如果你是还对PHP SESSION 原理不太熟悉,
请参看:《SESSION NAME引用SESSION ID的问题(Discuz! 用户(自动)登录原理)》
转自:http://blog.sina.com.cn/s/blog_79d4a75e010190g7.html
更多: