CodeIgniter框架源码笔记(13)——SESSION之文件File驱动实现
来源:互联网 发布:mac 翻墙 知乎 编辑:程序博客网 时间:2024/06/05 02:36
CI的文件驱动要满足以下三个条件:
1、驱动要实现open ,read ,write ,close ,destory ,gc六个方法。
session_start()时,调用了open(),read()方法。并有一定机率触发gc()方法。
session_commit()或session_write_close()时,触发write(),close()方法。
session_destory()会触发desotry()方法。
这六个方法实现的功能如下:
open:获取并创建文件保存的路径
read: 根据session_id读取或创建session_id对应的文件,获取文件操作指针并加锁flock
write:session内容变更时,将内容写入session_id对应的文件。该写入是全量写。session_data的内容包括新增的session键值对以及已经存在的键值对。
close:释放(flock)文件锁,并关闭(fclose)文件指针。
destory:删除服务端session_id对应的文件,并清除客户端对应session_id的cookie信息
gc:判断文件是否过期:根据文件最后一次修改时间(filemtime())和当前时间对比判断文件对应的session是否过期。该方法在session_start时一定机率调用。
2、驱动要支持session_regenerate_id()。
该方法重新生成一个新的session_id并创建以此session_id命名的文件。然后将原来老的session_id文件中保存的内容拷入新的文件中。最后删除老session_id所在的文件。
3、驱动要实现session锁:这里采用文件锁方式。
实现:文件驱动
class CI_Session_files_driver extends CI_Session_driver implements SessionHandlerInterface { //文件保存路径 protected $_save_path; //文件操作句柄 protected $_file_handle; //文件名 protected $_file_path; //是否新文件标识 protected $_file_new; // ------------------------------------------------------------------------ //构造函数 public function __construct(&$params) { parent::__construct($params); //初始化文件保存路径,根据配置文件设置php.ini中的session.save_path选项 if (isset($this->_config['save_path'])) { $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\'); //根据配置文件设置php.ini中的session.save_path选项 ini_set('session.save_path', $this->_config['save_path']); } else { //如果配置文件中的$config['sess_save_path']不存在,则使用当前ini中默认的路径 $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\'); } } // ------------------------------------------------------------------------ //open方法 //第一个参数$save_path对应的是ini_get('session.save_path') //第二个参数$name对应的是ini_get('session.name') public function open($save_path, $name) { //如果文件中径不存在,尝试创建 if ( ! is_dir($save_path)) { if ( ! mkdir($save_path, 0700, TRUE)) { //如果无法创建目录, throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created."); } } //如果目录不可写,抛出异常 elseif ( ! is_writable($save_path)) { throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process."); } //生成文件保存的路径,这里给文件保存目录加上一点料,避免冲突 $this->_config['save_path'] = $save_path; $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR .$name // we'll use the session cookie name as a prefix to avoid collisions .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : ''); return $this->_success; } // ------------------------------------------------------------------------ //read //参数$session_id对应的是session_id()的值 public function read($session_id) { //如果session_id()对应的文件操作指针为空 if ($this->_file_handle === NULL) { // Just using fopen() with 'c+b' mode would be perfect, but it is only // available since PHP 5.2.6 and we have to set permissions for new files, // so we'd have to hack around this ... //建议打开文件时使用c+模式,因为该模式当文件存在时不会删除文件原有内容 (w+模式下会清空原文件内容) //但是该模式只有PHP 5.2.6后有效,所以我们不得不根据文件是否存在而做不同的操作。 //文件不存在就用'w+'模式,文件存在就用'r+'模式 //当请求的Session文件不存在,则采用'w+b'读写模式创建文件,获取操作句柄 if (($this->_file_new = ! file_exists($this->_file_path.$session_id)) === TRUE) { //采用'w+b'模式打开文件,获取操作句柄 if (($this->_file_handle = fopen($this->_file_path.$session_id, 'w+b')) === FALSE) { //如果未能创建成功,则返回失败 log_message('error', "Session: File '".$this->_file_path.$session_id."' doesn't exist and cannot be created."); return $this->_failure; } } //如果请求的Session文件存在,则采用'r+b'读写模式打开文件,获取操作句柄 elseif (($this->_file_handle = fopen($this->_file_path.$session_id, 'r+b')) === FALSE) { //如果未能读取成功,则返回失败 log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'."); return $this->_failure; } //至此已成功获取文件指针,并赋给$this->_file_handle //锁定文件指针对像$this->_file_handle(LOCK_EX是独占锁定) //注意:释放锁(LOCK_UN)放在了close()函数中 if (flock($this->_file_handle, LOCK_EX) === FALSE) { //没锁定成功,记录日志,释放文件指针,然后返回失败. log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'."); fclose($this->_file_handle); $this->_file_handle = NULL; return $this->_failure; } // Needed by write() to detect session_regenerate_id() calls //将$session_id赋给对像的属性$this->_session_id //在session_regenerate_id()更改sessionid后,在write方法中用得到,这里保存了老的sessionid $this->_session_id = $session_id; //如果是新生成的文件,则设置文件权限600 if ($this->_file_new) { //只给读写权限,没有执行权限 chmod($this->_file_path.$session_id, 0600); $this->_fingerprint = md5('');//摘要是空字符的md5 return ''; } } // We shouldn't need this, but apparently we do ... // See https://github.com/bcit-ci/CodeIgniter/issues/4039 //如果$this->_file_handle === FALSE,则返回失败。 //这是git上一叫aanbar的小哥发现的,然后补上了$this->_file_handle === FALSE这个判断, //因为fopen成功时返回文件指针,如果打开失败返回 FALSE elseif ($this->_file_handle === FALSE) { return $this->_failure; } else { //如果指针不为空 //将文件内部offset指针重新指向开头 rewind($this->_file_handle); } $session_data = ''; //读取内容 for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer)) { if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE) { break; } $session_data .= $buffer; } //根据内容生成文件摘要 $this->_fingerprint = md5($session_data); return $session_data; } // Write 注意:Session的写入都是全量写,不是增量写 //参数$session_id对应的是session_id()的值 //参数$session_data不只是当前待写入的数据,它包含整个SESSION已保存的数据+当前要写入的数据 public function write($session_id, $session_data) { /***************** session_regenerate_id()处理 开始 *****************/ //如果程序调用了session_regenerate_id(),就会造成函数调用之后的$session_id(参数$session_id)和函数调用之前的$session_id( $this->_session_id)不一致。 //这时我们需要关闭旧的文件指针,打开新的文件获取操作指针 //这里的if条件语句其实就是个短路操作,分解开就是 /*if ($session_id !== $this->_session_id){ $close_flag=$this->close();//调用close关闭旧的指针 $read_flag=$this->read($session_id);//read函数参数为新的$session_id,从而创建新的文件 //上述两步中有一步出错,则返回失败。 //实际上 OR 也是短路操作,第一个$close_flag===$this->_failure的话,就不会再往后面执行$this->read($session_id) //为说明思路,先忽略这点 if($close_flag=== $this->_failure OR $read_flag===$this->_failure) return $this->_failure; }*/ if ($session_id !== $this->_session_id && ($this->close() === $this->_failure OR $this->read($session_id) === $this->_failure)) { return $this->_failure; } //如果$this->_file_handle)不是资源类型,则返回错误 if ( ! is_resource($this->_file_handle)) { return $this->_failure; } //如果当前请求的session内容摘要和$session_data是一样的,那么说明生成新的sessionid文件成功 //并用用touch函数测试一下文件是否存在,同时检测$this->_file_new标记(该标记在read中文件不存在需要新创建时会被设置为true) //如果条件都满足,就返回成功了 elseif ($this->_fingerprint === md5($session_data)) { return ( ! $this->_file_new && ! touch($this->_file_path.$session_id)) ? $this->_failure : $this->_success; } /***************** session_regenerate_id()处理 结束 *****************/ //如果是现成的文件,那么先清空内容 if ( ! $this->_file_new) { //清空文件内容 ftruncate($this->_file_handle, 0); //将文件内部offset指针重新指向开头 rewind($this->_file_handle); } //Session的写入都是全量写,不是增量写 //把$session_data内容写入文件。 if (($length = strlen($session_data)) > 0) { for ($written = 0; $written < $length; $written += $result) { if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE) { break; } } if ( ! is_int($result)) { $this->_fingerprint = md5(substr($session_data, 0, $written)); log_message('error', 'Session: Unable to write data.'); return $this->_failure; } } //获取SESSION内容摘要 $this->_fingerprint = md5($session_data); return $this->_success; } // Close //close()在当前请求的程序执行完毕后执行,或 在调用session_commit(),session_write_close()时执行 public function close() { if (is_resource($this->_file_handle)) { //释放文件锁 flock($this->_file_handle, LOCK_UN); //释放文件指针 fclose($this->_file_handle); //清空变量 $this->_file_handle = $this->_file_new = $this->_session_id = NULL; } return $this->_success; } // Destroy public function destory($session_id) { //调用close()方法 if ($this->close() === $this->_success) { if (file_exists($this->_file_path.$session_id)) { //删除对应的客户端cookie $this->_cookie_destroy(); //删除服务端文件 return unlink($this->_file_path.$session_id) ? $this->_success : $this->_failure; } return $this->_success; } //调用close()方法失败 elseif ($this->_file_path !== NULL) { //清除 PHP 缓存的该文件信息, is_file(),is_dir(), file_exists()都有影响 clearstatcache(); //重复上面的语句,再删除一次 if (file_exists($this->_file_path.$session_id)) { $this->_cookie_destroy(); return unlink($this->_file_path.$session_id) ? $this->_success : $this->_failure; } return $this->_success; } return $this->_failure; } // ------------------------------------------------------------------------ //gc方法。当session_start()时有机率调用,删除过期文件 public function gc($maxlifetime) { if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE) { log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'."); return $this->_failure; } $ts = time() - $maxlifetime; //确定session文件名的正则规定,免得误删文件 $pattern = sprintf('/^%s[0-9a-f]{%d}$/', preg_quote($this->_config['cookie_name'], '/'), ($this->_config['match_ip'] === TRUE ? 72 : 40) ); while (($file = readdir($directory)) !== FALSE) { // If the filename doesn't match this pattern, it's either not a session file or is not ours //根据创建时间判断是否过期 if ( ! preg_match($pattern, $file) OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file) OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE OR $mtime > $ts) { continue; } unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file); } closedir($directory); return $this->_success; }}
- CodeIgniter框架源码笔记(13)——SESSION之文件File驱动实现
- CodeIgniter框架源码笔记(14)——SESSION之文件Mysql驱动实现
- CodeIgniter框架源码笔记(15)——SESSION之文件Redis驱动实现
- CodeIgniter框架源码笔记(11)——SESSION类之闪出数据FlashData实现
- CodeIgniter框架源码笔记(12)——SESSION类之临时数据TempData实现
- CodeIgniter框架源码笔记(10)——SESSION类之用户接口CI_SESSION
- CodeIgniter框架源码笔记(3)——每次请求的总调度师傅:引导文件CodeIgniter.php
- CodeIgniter框架源码笔记(1)——回忆:准备
- PHP之CodeIgniter框架SESSION是怎么实现的
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
- CodeIgniter框架源码学习之框架初始化文件--CodeIgniter.php
- CI框架源码完全分析之核心文件Codeigniter.php
- CI框架源码完全分析之核心文件Codeigniter.php
- CI框架源码解析二之引导文件CodeIgniter.php
- CodeIgniter框架源码笔记(2)——请求的接收者:框架入口index.php
- CodeIgniter框架源码笔记(5)——识别多种URI风格:地址解析类URI.php
- CodeIgniter框架源码笔记(6)——支持友好的URI地址:路由类Router.php
- CodeIgniter框架源码笔记(7)——强大的配置管理器:配置类Config.php
- MD5加密原理
- 微博
- javascript设计模式-module(模块)模式
- 华硕pro系列笔记本,右击文件出现闪退现象解决办法
- git stash使用
- CodeIgniter框架源码笔记(13)——SESSION之文件File驱动实现
- 汉字存储
- 使用cornerstone上传忽略.a文件的解决办法
- 基本排序算法的Python实现
- 算术运算错误<error-page>配置文件的测试
- 数据追加--解决listview上啦刷新导致数据直接替换没有添加
- PHP语法分析器:RE2C && BISON 总结
- c# wiform程序通过webservice上传图片到服务器
- jsp操作MySQL实现查询/插入/删除功能示例