php将会话保存在数据库里
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
更多:
http://www.cnblogs.com/xingmeng/p/3223043.html
http://blog.snsgou.com/post-472.html
php将会话保存在数据库里的更多相关文章
- 解决secureCRT 数据库里没有找到防火墙 '无' 此会话降尝试不通过防火墙进行连接。
解决secureCRT 数据库里没有找到防火墙 '无' 此会话降尝试不通过防火墙进行连接.的方法 中文版的CRT由于汉化的问题(把null翻译成无了),导致每次打开都会有个防火墙的错误提示:数据库里没 ...
- Django数据操作F和Q、model多对多操作、Django中间件、信号、读数据库里的数据实现分页
models.tb.objects.all().using('default'),根据using来指定在哪个库里查询,default是settings中配置的数据库的连接名称. 外话:django中引 ...
- 解决secureCRT数据库里没有找到防火墙 '无'问题
中文版的secureCRT由于汉化的问题(把null翻译成无了),导致每次打开都会有个防火墙的错误提示:数据库里没有找到防火墙 '无' 此会话将尝试不通过防火墙进行连接.出现这个错误的原因是在secu ...
- Java Hour 47 WeatherInfo 保存到数据库
经历了上周简单的休整以后,我们继续Hibernate 之旅. 保存到数据库 private void saveWeatherInfo(Weatherinfo weatherInfo) { // Sav ...
- PHP SESSION 保存到数据库
PHP SESSION 的工作原理 在客户端(如浏览器)登录网站时,被访问的 PHP 页面可以使用 session_start() 打开 SESSION,这样就会产生客户端的唯一标识 SESSION ...
- 解决secureCRT数据库里没有找到防火墙 '无'问题,转自:http://jingyan.baidu.com/article/9989c74601274bf649ecfe74.html
中文版的secureCRT由于汉化的问题(把null翻译成无了),导致每次打开都会有个防火墙的错误提示:数据库里没有找到防火墙 '无' 此会话将尝试不通过防火墙进行连接.出现这个错误的原因是在secu ...
- thinkphp从数据库里的html代码显示页面不解析
首先,这个问题不应该出现在这里,因为以前在用ThinkPHP3.1.2的时候,利用富文本编辑器保存文本后,直接从数据库里面取出的数据都能正常显示,改用ThinkPHP3.2.3之后,thinkphp从 ...
- 芝麻HTTP:Python爬虫实战之抓取爱问知识人问题并保存至数据库
本次为大家带来的是抓取爱问知识人的问题并将问题和答案保存到数据库的方法,涉及的内容包括: Urllib的用法及异常处理 Beautiful Soup的简单应用 MySQLdb的基础用法 正则表达式的简 ...
- 解决SecureCRT中文版"数据库里没找到防火墙'无'"的错误提示
问题描述: 最近从同事那拷贝到一个中文版的SecureCRT,但是每次打开都会有个防火墙的错误提示,“数据库里没找到防火墙“无”.此会话将尝试不通过防火墙进行连接. 出现这个错误的原因是在Secure ...
随机推荐
- openwrt l7过滤qos配置
openwrt l7过滤qos配置 电梯直达 1# 本帖最后由 木鸟 于 2010-7-27 10:22 编辑 openwrt的qos基于hsfc.提供了分类标记,流量控制等功能,可能还有整形 ...
- BZOJ1270: [BeijingWc2008]雷涛的小猫
1270: [BeijingWc2008]雷涛的小猫 Time Limit: 50 Sec Memory Limit: 162 MBSubmit: 836 Solved: 392[Submit][ ...
- bzoj3039 玉蟾宫
Description 有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地.这片土地被分成N*M个格子,每个格子里写着'R'或者' ...
- 利用Visual Studio寻找C#程序必要的运行库文件
在工程打包中,有时候很头痛的就是运行所需要的库文件不能够全面的包含进来,特别是有时候调用了一系列外部扩展.对于这些问题,我们可以借用Visual Studio的打包功能帮助我们寻找软件运行必须的库文件 ...
- POJ3580---SuperMemo (Splay)
各种操作,区间更新,求最值.翻转.插入.删除.当然是Splay这种神器了. 主要是 revolve这个操作,其实也就是3个区间翻转放到一块, 比如 REVOLVE x y T,T %= (y-x+1) ...
- poj3237--Tree 树链剖分
题意:三种操作 ①修改第i条边的权值为val,②把u到v路径上的所有边的权值 去相反数③求u 到v路径上最大的边权 线段树的区间更新还是不熟练,,一直搞不对调试了好久还是没对,最后还是看的kuangb ...
- 【转】USB协议架构及驱动架构
1. USB协议 1.1 USB主机系统 在USB主机系统中,通过根集线器与外部USB从机设备相连的处理芯片,称为USB主机控制器.USB主机控制器包含硬件.软件和固件一部分. 1.2 USB设备系统 ...
- Hive MapJoin
摘要 MapJoin是Hive的一种优化操作,其适用于小表JOIN大表的场景,由于表的JOIN操作是在Map端且在内存进行的,所以其并不需要启动Reduce任务也就不需要经过shuffle阶段,从而能 ...
- CentOS 6.3中安装OpenCV2.3.1
下面为自己测试可用的OpenCV在Linux下的安装步骤 .检查并安装相关程序,确保gtk安装成功,否则无法显示图片 yum install gcc-c++ yuminstall gtk-devel. ...
- 并发情况下synchronized死锁
存在缺陷的代码: public class DataPropertyIdAndNameRepositoryImpl{ /** 发布标志 */ private volatile boolean publ ...