默认情况下,PHP 使用内置的文件会话保存管理器(files)来完成会话的保存。我们无需设置,PHP默认将session以文件的形式保存到服务器。

通过调用函数 session_start() 即可手动开始一个会话。如果配置项 session.auto_start 设置为1, 那么请求开始的时候,会话会自动开始。

PHP也提供了自定义会话保存管理器功能。有时候我们希望session可以保存到其他地方,如数据库。

自定义Session存储函数

如果需要在数据库中或者以其他方式存储会话数据, 需要使用 session_set_save_handler() 函数来创建一系列用户级存储函数。

函数 session_set_save_handler() 的参数即为在会话生命周期内要调用的一组回调函数: openreadwrite 以及 close。 还有一些回调函数被用来完成垃圾清理:destroy 用来删除会话, gc 用来进行周期性的垃圾收集。

PHP手册上说明了实现原理:

会话开始的时候,PHP 会调用 open 管理器,然后再调用 read 回调函数来读取内容,该回调函数返回已经经过编码的字符串。 然后 PHP 会将这个字符串解码,并且产生一个数组对象,然后保存至 $_SESSION 超级全局变量。

当 PHP 关闭的时候(或者调用了 session_write_close() 之后), PHP 会对 $_SESSION 中的数据进行编码, 然后和会话 ID 一起传送给 write 回调函数。 write 回调函数调用完毕之后,PHP 内部将调用 close 回调函数。

销毁会话时,PHP 会调用 destroy回调函数。

根据会话生命周期时间的设置,PHP 会不时地调用 gc 回调函数。 该函数会从持久化存储中删除超时的会话数据。 超时是指会话最后一次访问时间距离当前时间超过了 $lifetime 所指定的值。

需要实现:

session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )

MySQL存储session示例:

<?php
/*
CREATE TABLE IF NOT EXISTS `sessions` (
`id` varchar(40) NOT NULL,
`ip_address` varchar(45) NOT NULL DEFAULT '',
`timestamp` int(10) unsigned DEFAULT 0 NOT NULL,
`data` blob NOT NULL,
KEY `sessions_timestamp` (`timestamp`)
);
*/ $con = mysqli_connect("127.0.0.1", "user" , "pass", "session");
function open($save_path, $session_name) {
return true;
} function close() {
return true;
} function read($id, $con) {
if ($result = mysqli_query($con, sprintf("select * from sessions where `id`='%s' and `timestamp` > '%s'", $id, time()))) {
if ($row = mysqli_fetch_row($result)) {
return $row["data"];
}
}else {
return "";
}
} function write($id, $data, $con) {
$expire = time() + SESSION_MAXLIFETIME; $sql = 'INSERT INTO `sessions` (`id`, `data`, `timestamp`) '
. 'values (%s, %s, %s) '
. 'ON DUPLICATE KEY UPDATE data = %s, timestamp = %s'; if ($result = mysqli_query($con, $con, sprintf($sql, $id, $data, $expire, $data, $expire))) {
return true;
} else {
return false;
}
} function destroy($id, $con) {
if ($result = mysqli_query($con, sprintf("delete * from sessions where id=%s", $id))) {
return true;
}else {
return false;
}
} function gc($maxlifetime, $con) {
if ($result = mysqli_query($con, sprintf("delete * from sessions where timestamp < %s", time()))) {
return true;
} else {
return false;
}
} session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
session_start();

当然,也支持类里面的方法:

<?php
class MySQLSession{
private $con; function open($save_path, $session_name) {
$this->con = mysqli_connect("127.0.0.1", "user" , "pass", "session");
} function close() {
mysqli_close($this->con);
} function read($id) {
if ($result = mysqli_query($this->con, sprintf("select * from sessions where `id`=%s and `timestamp` > %s", $id, time()))) {
if ($row = mysqli_fetch_row($result)) {
return $row["data"];
}
}else {
return "";
}
} function write($id, $data) {
$expire = time() + SESSION_MAXLIFETIME; $sql = 'INSERT INTO `sessions` (`id`, `data`, `timestamp`) '
. 'values (%s, %s, %s) '
. 'ON DUPLICATE KEY UPDATE data = %s, timestamp = %s'; if ($result = mysqli_query($this->con, sprintf($sql, $id, $data, $expire, $data, $expire))) {
return true;
} else {
return false;
}
} function destroy($id) {
if ($result = mysqli_query($this->con, sprintf("delete * from sessions where id=%s", $id))) {
return true;
}else {
return false;
}
} function gc($maxlifetime) {
if ($result = mysqli_query($this->con, sprintf("delete * from sessions where timestamp < %s", time()))) {
return true;
} else {
return false;
}
}
} $handler = new MySQLSession();
session_set_save_handler(
array($handler, 'open'),
array($handler, 'close'),
array($handler, 'read'),
array($handler, 'write'),
array($handler, 'destroy'),
array($handler, 'gc')
);
// 下面这行代码可以防止使用对象作为会话保存管理器时可能引发的非预期行为
register_shutdown_function ( 'session_write_close' ); session_start();

自 PHP 5.4 开始,可以使用下面的方式来注册自定义会话存储函数:

bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] )

需要实现类里的open,write,read,destroy,gc,close方法。

<?php
class MySessionHandler implements SessionHandlerInterface{
public function open($save_path, $session_id)
{
return true;
} /**
* 写session
*/
public function write($session_id, $session_data)
{
// TODO: Implement write() method.
} /**
* 读取session
*/
public function read($session_id)
{
// TODO: Implement read() method.
} /**
* 删除指定session
*/
public function destroy($session_id)
{
// TODO: Implement destroy() method.
} /**
* 销毁过期session
*/
public function gc($maxlifetime)
{
// TODO: Implement gc() method.
} public function close()
{
return true;
}
} $handler = new MySessionHandler ();
session_set_save_handler ( $handler , true );
session_start ();

分布式存储

当多台服务器负载均衡使用时,这时候还在使用默认的文件存储session方式,就会造成session不同步。这时候我们可以把session存储到同一个地方,如上面的mysql存储session方式。下面看看memcache、redis等如何存储session。

一些 PHP 扩展提供了内置的会话管理器,例如:redis, memcache, 可以通过配置项 session.save_handler 来使用它们。

对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。

对于缓存类保存管理器,会将会话数据保存到配置项 session.save_path 所指定的地址。

Memcache

<?php
ini_set("session.save_handler", "memcache"); // memcache
ini_set("session.save_path", "127.0.0.1:11211"); // 不要tcp: session_start(); $mem = new memcache();
$mem->addServer('127.0.0.1', '11211'); //测试memcache是否正常
//$mem->add('uid', 6, 0, 3600);
//echo $mem->get('uid'); //设置一个session
//$_SESSION['uid'] = 10;
//var_dump($_SESSION); //查看session在memcache里的存储
echo $mem->get(session_id());

Redis

<?php
ini_set("session.save_handler", "redis"); // memcache
ini_set("session.save_path", "127.0.0.1:6379"); // 不要tcp: session_start(); $redis = new redis();
$redis->connect('127.0.0.1', '6379'); //测试redis是否正常
//$redis->set('uid', 6, 3600);
//echo $redis->get('uid'); //设置一个session
$_SESSION['uid'] = 10;
//var_dump($_SESSION); //查看session在redis里的存储
var_dump($redis->get('PHPREDIS_SESSION:'.session_id()));

实例

ThinkPHP3.13设置session入库

准备工作:

1、建立session表:

CREATE TABLE `thinkphp_session` (
`session_id` varchar(255) NOT NULL,
`session_expire` int(11) NOT NULL,
`session_data` blob,
UNIQUE KEY `session_id` (`session_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

这里的表名以实际为准。

2、修改配置文件

配置文件config.php中配置session的数据表,追加一下数据:

'SESSION_OPTIONS'       =>  array(
'type'=> 'db',//session采用数据库保存
'name' => 'PHPSESSION', //设置session名
'expire' => 3600 * 24 * 1, //SESSION保存时间
'use_trans_sid' => 1, //跨页传递
'use_only_cookies' => 0, //是否只开启基于cookies的session的会话方式
),
'SESSION_TABLE'=>'thinkphp_session',

3、确保相关驱动存在,位于Core\Extend\Driver\Session:

SessionDb.class.php

该文件默认使用mysql系列函数连接,需要修改为mysqli:

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- defined('THINK_PATH') or exit();
/**
* 数据库方式Session驱动
* CREATE TABLE think_session (
* session_id varchar(255) NOT NULL,
* session_expire int(11) NOT NULL,
* session_data blob,
* UNIQUE KEY `session_id` (`session_id`)
* );
* @category Extend
* @package Extend
* @subpackage Driver.Session
* @author liu21st <liu21st@gmail.com>
*/
class SessionDb { /**
* Session有效时间
*/
protected $lifeTime = ''; /**
* session保存的数据库名
*/
protected $sessionTable = ''; /**
* 数据库句柄
*/
protected $hander = array(); /**
* 打开Session
* @access public
* @param string $savePath
* @param mixed $sessName
*/
public function open($savePath, $sessName) {
$this->lifeTime = C('SESSION_EXPIRE')?C('SESSION_EXPIRE'):ini_get('session.gc_maxlifetime');
$this->sessionTable = C('SESSION_TABLE')?C('SESSION_TABLE'):C("DB_PREFIX")."session";
//分布式数据库
$host = explode(',',C('DB_HOST'));
$port = explode(',',C('DB_PORT'));
$name = explode(',',C('DB_NAME'));
$user = explode(',',C('DB_USER'));
$pwd = explode(',',C('DB_PWD'));
if(1 == C('DB_DEPLOY_TYPE')){
//读写分离
if(C('DB_RW_SEPARATE')){
$w = floor(mt_rand(0,C('DB_MASTER_NUM')-1));
if(is_numeric(C('DB_SLAVE_NO'))){//指定服务器读
$r = C('DB_SLAVE_NO');
}else{
$r = floor(mt_rand(C('DB_MASTER_NUM'),count($host)-1));
}
//主数据库链接
$hander = mysqli_connect(
$host[$w].(isset($port[$w])?':'.$port[$w]:':'.$port[0]),
isset($user[$w])?$user[$w]:$user[0],
isset($pwd[$w])?$pwd[$w]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$w])?$name[$w]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander[0] = $hander;
//从数据库链接
$hander = mysqli_connect(
$host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]),
isset($user[$r])?$user[$r]:$user[0],
isset($pwd[$r])?$pwd[$r]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$r])?$name[$r]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander[1] = $hander;
return true;
}
}
//从数据库链接
$r = floor(mt_rand(0,count($host)-1));
$hander = mysqli_connect(
$host[$r].(isset($port[$r])?':'.$port[$r]:':'.$port[0]),
isset($user[$r])?$user[$r]:$user[0],
isset($pwd[$r])?$pwd[$r]:$pwd[0]
);
$dbSel = mysqli_select_db(
$hander,
isset($name[$r])?$name[$r]:$name[0]
);
if(!$hander || !$dbSel)
return false;
$this->hander = $hander;
return true;
} /**
* 关闭Session
* @access public
*/
public function close() {
if(is_array($this->hander)){
$this->gc($this->lifeTime);
return (mysqli_close($this->hander[0]) && mysqli_close($this->hander[1]));
}
$this->gc($this->lifeTime);
return mysqli_close($this->hander);
} /**
* 读取Session
* @access public
* @param string $sessID
*/
public function read($sessID) {
$hander = is_array($this->hander)?$this->hander[1]:$this->hander;
$res = mysqli_query($hander, "SELECT session_data AS data FROM ".$this->sessionTable." WHERE session_id = '$sessID' AND session_expire >".time());
if($res) {
$row = mysqli_fetch_assoc($res);
return $row['data'];
}
return "";
} /**
* 写入Session
* @access public
* @param string $sessID
* @param String $sessData
*/
public function write($sessID,$sessData) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
$expire = time() + $this->lifeTime;
mysqli_query($hander, "REPLACE INTO ".$this->sessionTable." ( session_id, session_expire, session_data) VALUES( '$sessID', '$expire', '$sessData')");
if(mysqli_affected_rows($hander))
return true;
return false;
} /**
* 删除Session
* @access public
* @param string $sessID
*/
public function destroy($sessID) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
mysqli_query($hander, "DELETE FROM ".$this->sessionTable." WHERE session_id = '$sessID'");
if(mysqli_affected_rows($hander))
return true;
return false;
} /**
* Session 垃圾回收
* @access public
* @param string $sessMaxLifeTime
*/
public function gc($sessMaxLifeTime) {
$hander = is_array($this->hander)?$this->hander[0]:$this->hander;
mysqli_query($hander, "DELETE FROM ".$this->sessionTable." WHERE session_expire < ".time());
return mysqli_affected_rows($hander);
} /**
* 打开Session
* @access public
*/
public function execute() {
session_set_save_handler(array(&$this,"open"),
array(&$this,"close"),
array(&$this,"read"),
array(&$this,"write"),
array(&$this,"destroy"),
array(&$this,"gc"));
}
}

经过测试,使用了函数session_set_save_handler后,session.save_handle后的值被修改为了user

Session自定义存储及分布式存储的更多相关文章

  1. 可灵活扩展的自定义Session状态存储驱动

    Session是互联网应用中非常重要的玩意儿,对于超过单台部署的站点集群,都会存在会话共享的需求.在web.config中,微软提供了sessionstate节点来定义不同的Session状态存储方式 ...

  2. ASP.net 中关于Session的存储信息及其它方式存储信息的讨论与总结

    通过学习和实践笔者总结一下Session 的存储方式.虽然里面的理论众所周知,但是我还是想记录并整理一下.作为备忘录吧.除了ASP.net通过Web.config配置的方式,还有通过其它方式来存储的方 ...

  3. session的存储方式和配置

    Session又称为会话状态,是Web系统中最常用的状态,用于维护和当前浏览器实例相关的一些信息.我们控制用户去权限中经常用到Session来存储用户状态,这篇文章会讲下Session的存储方式.在w ...

  4. Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程

    待解决的问题 Spring session(redis存储方式)监听导致创建大量redisMessageListenerContailner-X线程 解决办法 为spring session添加spr ...

  5. [转]mvc3 使用session来存储类来存储用户登陆信息

    mvc3 使用session来存储类来存储用户登陆信息 2013-08-26 09:48:56|  分类: NET开发 |举报 |字号 订阅   项目之前的登陆机制是这样的:用户登陆后初始化一个类,类 ...

  6. 多台web如何共享session进行存储(转载)

    session的存储了解以前是怎么做的,搞清楚了来龙去脉,才会明白进行共享背后的思想和出发点.我喜欢按照这样的方式来问(或者去搞清楚):为什么要session要进行共享,不共享会什么问题呢? php中 ...

  7. PHP学习笔记:使用session来存储用户的登录信息

    session可以用来存储多种类型的数据,因此具有很多的用途,常用来存储用户的登录信息,购物车数据,或者一些临时使用的暂存数据等. 用户在登录成功以后,通常可以将用户的信息存储在session中,一般 ...

  8. 修改session的存储机制

    <?php  //修改session的存储机制 //最起码应该有一个 读方法, 和一个 写方法. //1, 我们先去建立 读方法 和 写方法. //2, 告知session系统,使用我们的方法完 ...

  9. JAVA之旅(二十)—HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习

    JAVA之旅(二十)-HashSet,自定义存储对象,TreeSet,二叉树,实现Comparator方式排序,TreeSet小练习 我们继续说一下集合框架 Set:元素是无序(存入和取出的顺序不一定 ...

随机推荐

  1. 国内及Github优秀开发人员列表

    自从入了Android软件开发的行道,解决问题和学习过程中免不了会参考别人的思路,浏览博文和门户网站成了最大的入口.下面这些列表取名为:国内及Github优秀开发人员列表,就是浏览后的成果. 虽然下述 ...

  2. Ansible常用模块

    http://liumissyou.blog.51cto.com/4828343/1749121

  3. Java中实现PHP中的urlencode与rawurlencode

    php手册中对urlencode这样说明 在java中 URLEncoder做了这样注释 也就是说java中对星号"*"是不进行编码的 也就是说URLEncoder之后还是&quo ...

  4. 论ubuntu的作死技巧

    此处记录自己弄崩系统的几大杀器,长期更新. 1. sudo apt-get autoremove

  5. How to Use Lucene.NET with Windows Azure SQL Database

    http://social.technet.microsoft.com/wiki/contents/articles/2367.how-to-use-lucene-net-with-windows-a ...

  6. vb小菜一枚-----了解“类型推理”

    局部类型推理 (Visual Basic) Visual Studio 2013   其他版本   Visual Basic 编译器使用类型推理来确定未使用 As 子句声明的局部变量的数据类型. 编译 ...

  7. 图的邻接多重表和搜索(C++版本)

    最近在学数据结构,学到图这一章,网上的C++版本的代码乱得不行,所以自己写了一个完整C++版本的放这里. 用邻接多重表表示一个无向图,并给出DFS和BFS搜索代码.邻接多重表好处就是贼直观,几条边就几 ...

  8. TreeMap源码分析

    MapClassDiagram

  9. [原] XAF How to Edit multiple objects in a ListViewAndDetailView

    2014年好久没有更新Blog了,工作调换了,很少用XAF,但还是很关注XAF的发展和学习,对中国的中小企业数据管理软件开发真的太实用了!! 功能比较简单,但很实用,直接上图和代码! ListView ...

  10. JS正则表达式验证数字

    <script type="text/javascript"> function validate(){ var reg = new RegExp("^[0- ...