CodeIgniter框架源码笔记(14)——SESSION之文件Mysql驱动实现

来源:互联网 发布:linux 运行lua脚本 编辑:程序博客网 时间:2024/05/29 20:02

配置选项save_path用来作为表名。
存储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,这样做对于应用程序来说是好事还是坏事???

<?phpclass 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();    }}
0 0
原创粉丝点击