CodeIgniter框架源码笔记(14)——SESSION之Mysql驱动实现
2016-08-25 21:09
399 查看
配置选项save_path用来作为表名。
存储Session的表结构如下:
session_start()时,调用了open(),read()方法。并有一定机率触发gc()方法。
session_commit()或session_write_close()时,触发write(),close()方法。
session_destory()会触发desotry()方法。
1、驱动要实现open ,read ,write ,close ,destory ,gc六个方法。
open:连接数据库db_connect()
read: 根据session_id读取对应行记录data字段内容,并设置字符串锁SELECT GET_LOCK(‘sessionid’,300)。约定表字段id保存session_id,字段data保存session数据。
write:session内容变更时,更新session_id对应的行记录。如果不存在该行则insert数据,如果存在则update数据。
close:释放字符串锁SELECT RELEASE_LOCK(‘sessionid’)。
destory:delete表中对应sessionid的记录
gc:根据timestamp字段判断记录是否过期,若过期则delete记录。
2、驱动要支持session_regenerate_id()。
3、驱动要实现session锁:一条记录只允许一个http请求独占。CI采用数据库字符锁,mysql下是
实现:数据库驱动
三个必须:数据库必须是短连接、数据库必须禁止缓存,另CI要求数据库类必须实现CI_DB_query_builder抽像类。
疑问:如果同一个$session_id锁被另一个http进程占用了,按照CI框架的写法,那么本次http请求中sesstion_start()并不会阻塞并等待另一边释放锁,而是直接返回空的SESSION,这样做对于应用程序来说是好事还是坏事???
存储Session的表结构如下:
'id' => $session_id, 'ip_address' => $_SERVER['REMOTE_ADDR'], 'timestamp' => time(), 'data' => $session_data
session_start()时,调用了open(),read()方法。并有一定机率触发gc()方法。
session_commit()或session_write_close()时,触发write(),close()方法。
session_destory()会触发desotry()方法。
1、驱动要实现open ,read ,write ,close ,destory ,gc六个方法。
open:连接数据库db_connect()
read: 根据session_id读取对应行记录data字段内容,并设置字符串锁SELECT GET_LOCK(‘sessionid’,300)。约定表字段id保存session_id,字段data保存session数据。
write:session内容变更时,更新session_id对应的行记录。如果不存在该行则insert数据,如果存在则update数据。
close:释放字符串锁SELECT RELEASE_LOCK(‘sessionid’)。
destory:delete表中对应sessionid的记录
gc:根据timestamp字段判断记录是否过期,若过期则delete记录。
2、驱动要支持session_regenerate_id()。
3、驱动要实现session锁:一条记录只允许一个http请求独占。CI采用数据库字符锁,mysql下是
SELECT GET_LOCK('str',300) / SELECT RELEASE_LOCK
实现:数据库驱动
三个必须:数据库必须是短连接、数据库必须禁止缓存,另CI要求数据库类必须实现CI_DB_query_builder抽像类。
疑问:如果同一个$session_id锁被另一个http进程占用了,按照CI框架的写法,那么本次http请求中sesstion_start()并不会阻塞并等待另一边释放锁,而是直接返回空的SESSION,这样做对于应用程序来说是好事还是坏事???
<?php class CI_Session_database_driver extends CI_Session_driver implements SessionHandlerInterface { //db对像 protected $_db; //session_id对应记录是否存在标记。 protected $_row_exists = FALSE; //数据库平台,CI中有mysql和postgre protected $_platform; // ------------------------------------------------------------------------ //完成三个必须的判断 public function __construct(&$params) { parent::__construct($params); $CI =& get_instance(); //加载database数据库驱动进来。注:数据库的接口与实现放在system/databses目录下 isset($CI->db) OR $CI->load->database(); $this->_db = $CI->db; //数据库必须实现CI_DB_query_builder抽像类 if ( ! $this->_db instanceof CI_DB_query_builder) { throw new Exception('Query Builder not enabled for the configured database. Aborting.'); } //数据库必须是短连接 elseif ($this->_db->pconnect) { throw new Exception('Configured database connection is persistent. Aborting.'); } //数据库必须禁止缓存 elseif ($this->_db->cache_on) { throw new Exception('Configured database connection has cache enabled. Aborting.'); } //判断用的哪个类型的数据库 $db_driver = $this->_db->dbdriver.(empty($this->_db->subdriver) ? '' : '_'.$this->_db->subdriver); if (strpos($db_driver, 'mysql') !== FALSE) { $this->_platform = 'mysql'; } elseif (in_array($db_driver, array('postgre', 'pdo_pgsql'), TRUE)) { $this->_platform = 'postgre'; } // 设置表名 isset($this->_config['save_path']) OR $this->_config['save_path'] = config_item('sess_table_name'); } // ------------------------------------------------------------------------ //open()主要完成数据库连接工作connect() public function open($save_path, $name) { //调用db_connect连接数据库 if (empty($this->_db->conn_id) && ! $this->_db->db_connect()) { return $this->_failure; } return $this->_success; } // ------------------------------------------------------------------------ //读取session_id对应内容 public function read($session_id) { //抢占锁:该锁是由$session_id组合生成的字符串锁 //疑问:如果同一个$session_id锁被另一个http进程占用了,按照框架的写法,那么本次http请求中sesstion_start()并不会阻塞并等待另一边释放锁,而是直接返回空的SESSION //这样做对于应用程序来说是好事还是坏事??? if ($this->_get_lock($session_id) !== FALSE) { // Prevent previous QB calls from messing with our queries //将DB的查询请求复位 $this->_db->reset_query(); // Needed by write() to detect session_regenerate_id() calls //存储当前session_id,主要用于session_regenerate_id()调用时,在write方法中与方法参数中的新sessionid对比 $this->_session_id = $session_id; //查询:根据session_id查询data数据 $this->_db ->select('data') ->from($this->_config['save_path']) ->where('id', $session_id); //如果配置中约定了session必须匹配客户端IP,那么再加上IP一致性这个条件 if ($this->_config['match_ip']) { $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); } //如果没有对应的记录,则将_row_exists 标志设为FALSE,摘要为md5(''),返回'' if (($result = $this->_db->get()->row()) === NULL) { //将_row_exists 标志设为FALSE $this->_row_exists = FALSE; //摘要为md5('') $this->_fingerprint = md5(''); return ''; } // PostgreSQL's variant of a BLOB datatype is Bytea, which is a // PITA to work with, so we use base64-encoded data in a TEXT // field instead. //----------以下是查询到对应记录的处理---------------- //PostgreSQL下需要把结果用base64_decode编码后输出。 $result = ($this->_platform === 'postgre') ? base64_decode(rtrim($result->data)) : $result->data; //设置摘要,设置_row_exists标记 $this->_fingerprint = md5($result); $this->_row_exists = TRUE; return $result; } $this->_fingerprint = md5(''); return ''; } // ------------------------------------------------------------------------ // public function write($session_id, $session_data) { // Prevent previous QB calls from messing with our queries //查询之前都要进行复位。 $this->_db->reset_query(); // Was the ID regenerated? //处理session_regenerate_id()函数调用 //这时参数传进来的新的session_id与原来对像中保存的session_id不一致 if ($session_id !== $this->_session_id) { //写记录之前要做两件事: //1、首先释放锁。因为session_id变更了,所以要更新$session_id组合生成的字符串锁,原来的锁作废。 //2、用新的session_id重新加锁。 if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) { return $this->_failure; } //如果是传进来的session_id与之前对像属性保存的不一致,说明是session_regenerate_id //那么对应的新ID的记录是不存在的,所以设置$this->_row_exists = FALSE; $this->_row_exists = FALSE; $this->_session_id = $session_id; } //新老sessionid是一致的情况 //因为session_start()时触发的read()函数会加锁,所以$this->_lock到这里一定是TRUE, //如果是FALSE就说明当前的http请求在read时抢占锁失败,同名的sessionid锁已经被另一个http请求占有 elseif ($this->_lock === FALSE) { return $this->_failure; } //如果不存在session_id对应的记录,则新增记录,insert后并根据数据库返回的成功失败进行返回。 if ($this->_row_exists === FALSE) { $insert_data = array( 'id' => $session_id, 'ip_address' => $_SERVER['REMOTE_ADDR'], 'timestamp' => time(), 'data' => ($this->_platform === 'postgre' ? base64_encode($session_data) : $session_data) ); if ($this->_db->insert($this->_config['save_path'], $insert_data)) { $this->_fingerprint = md5($session_data); $this->_row_exists = TRUE; return $this->_success; } return $this->_failure; } //若存在session_id对应的记录,则更新$data和更新时间timestamp $this->_db->where('id', $session_id); if ($this->_config['match_ip']) { $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); } $update_data = array('timestamp' => time()); if ($this->_fingerprint !== md5($session_data)) { $update_data['data'] = ($this->_platform === 'postgre') ? base64_encode($session_data) : $session_data; } if ($this->_db->update($this->_config['save_path'], $update_data)) { $this->_fingerprint = md5($session_data); return $this->_success; } return $this->_failure; } // ------------------------------------------------------------------------ //close函数主要是释放字符锁 public function close() { //释放字符锁 return ($this->_lock && ! $this->_release_lock()) ? $this->_failure : $this->_success; } // ------------------------------------------------------------------------ /** * Destroy * * Destroys the current session. * * @param string $session_id Session ID * @return bool */ public function destroy($session_id) { if ($this->_lock) { // Prevent previous QB calls from messing with our queries //复位查询 $this->_db->reset_query(); //生成where条件 $this->_db->where('id', $session_id); if ($this->_config['match_ip']) { $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); } //删除表中对应的记录 if ( ! $this->_db->delete($this->_config['save_path'])) { return $this->_failure; } } //调用close()释放锁 if ($this->close() === $this->_success) { //清除客户端cookie $this->_cookie_destroy(); return $this->_success; } return $this->_failure; } // ------------------------------------------------------------------------ /** * Garbage Collector * * Deletes expired sessions * * @param int $maxlifetime Maximum lifetime of sessions * @return bool */ public function gc($maxlifetime) { // Prevent previous QB calls from messing with our queries $this->_db->reset_query(); //根据timestamp以及session生存周期session.gc_maxlifetime,删除超时的记录 return ($this->_db->delete($this->_config['save_path'], 'timestamp < '.(time() - $maxlifetime))) ? $this->_success : $this->_failure; } // ------------------------------------------------------------------------ /** * Get lock * * Acquires a lock, depending on the underlying platform. * * @param string $session_id Session ID * @return bool */ //覆盖抽像类中的_get_lock方法 //该方法实现对当前session操作加锁 protected function _get_lock($session_id) { //mysql库 if ($this->_platform === 'mysql') { $arg = $session_id.($this->_config['match_ip'] ? '_'.$_SERVER['REMOTE_ADDR'] : ''); //采用数据库字符锁SELECT GET_LOCK('".$arg."', 300) //在没有释放锁之前,对同一个字符串加锁就会失败返回false if ($this->_db->query("SELECT GET_LOCK('".$arg."', 300) AS ci_session_lock")->row()->ci_session_lock) { //更新对像属性$this->_lock $this->_lock = $arg; return TRUE; } //走到这里说明同名字符串锁被占用,本次申请加锁失败。 return FALSE; } //postgre elseif ($this->_platform === 'postgre') { $arg = "hashtext('".$session_id."')".($this->_config['match_ip'] ? ", hashtext('".$_SERVER['REMOTE_ADDR']."')" : ''); if ($this->_db->simple_query('SELECT pg_advisory_lock('.$arg.')')) { $this->_lock = $arg; return TRUE; } return FALSE; } //如果不是上述两种类型库,则调用父函数返回。不过意义不大 return parent::_get_lock($session_id); } // ------------------------------------------------------------------------ /** * Release lock * * Releases a previously acquired lock * * @return bool */ //覆盖抽像类中的_release_lock方法 //该方法实现对当前session操作解锁 //如果锁被释放,那么无论$this->_lock之前是什么值,这时都要置为FALSE。 protected function _release_lock() { //通过$this->_lock判断当前对像是否加锁 //如果当前对像没有锁,就立即返回 if ( ! $this->_lock) { return TRUE; } //Mysql下 if ($this->_platform === 'mysql') { //释放由$session_id组合生成的字符串锁 //注意在加锁时,$this->_lock保存了锁的字符串。 if ($this->_db->query("SELECT RELEASE_LOCK('".$this->_lock."') AS ci_session_lock")->row()->ci_session_lock) { $this->_lock = FALSE; return TRUE; } return FALSE; } elseif ($this->_platform === 'postgre') { if ($this->_db->simple_query('SELECT pg_advisory_unlock('.$this->_lock.')')) { $this->_lock = FALSE; return TRUE; } return FALSE; } return parent::_release_lock(); } }
相关文章推荐
- CodeIgniter框架源码笔记(15)——SESSION之Redis驱动实现
- CodeIgniter框架源码笔记(13)——SESSION之文件File驱动实现
- CodeIgniter框架源码笔记(11)——SESSION类之闪出数据FlashData实现
- CodeIgniter框架源码笔记(10)——SESSION类之用户接口CI_SESSION
- 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)
- 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)
- 从零开始写javaweb框架笔记14-搭建轻量级JAVAWEB框架-实现Bean容器
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
- CodeIgniter框架源码笔记(7)——强大的配置管理器:配置类Config.php
- CodeIgniter框架源码笔记(12)——SESSION类之临时数据TempData实现
- CodeIgniter框架源码笔记(8)——性能调试:基准测试类Benchmark.php
- 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)
- CodeIgniter框架源码笔记(5)——识别多种URI风格:地址解析类URI.php
- CodeIgniter框架源码笔记(2)——请求的接收者:框架入口index.php
- PHP之CodeIgniter框架SESSION是怎么实现的
- 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)
- CodeIgniter框架源码笔记(3)——每次请求的总调度师傅:引导文件CodeIgniter.php
- CodeIgniter框架源码笔记(4)——负责屏幕上呈现的内容:输出类Output.php
- 从零开始写javaweb框架笔记14-搭建轻量级JAVAWEB框架-实现Bean容器
- 利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)