即使session_save_handler被自己的类或者方法重写,write与read的出入数据都还是被序列化的,而且被session序列化不是一般的序列化...还是不能解解决memcached保存session数据为json的格式。
貌似php_memcached扩展有个option是可以选择序列化方式的有json方式的选项,这个还没试过,就怕只有是test|s:4:"data"中右边的部分json编码,再就是自己实现session的过程。。。
session被重新handler后的执行session_start的顺序:
在类析构函数里执行session_write_close函数
open
read--eke723bndobd24jhtn4sivb1i6
gc(有一定的概率执行)
write--eke723bndobd24jhtn4sivb1i6,
close
执行:$_SESSON['test']='data';$_SESSION['test2']=2;赋值操作:
open
read--eke723bndobd24jhtn4sivb1i6
------------------
write--eke723bndobd24jhtn4sivb1i6,--test|s:4:"data";test2|i:2;
close
不管session被赋值几次,都只有一次读取和写入操作。
执行:echo $_SESSION['test'];echo $_SESSION['test2'];
open
read--eke723bndobd24jhtn4sivb1i6
------------------
12 write--eke723bndobd24jhtn4sivb1i6,--test|i:1;test2|i:2;
close
读取之后也要最后写入一次。即使没有session写操作。
附上session_save_handler类:
class Cache_Session_Handler { public $_memcache; public function __construct() { $this->_memcache = new Memcache; $this->_memcache->addServer( '192.168.11.23', 11211, 1 ); session_set_save_handler( array($this, "open"), array($this, "close"), array($this, "read"), array($this, "write"), array($this, "destroy"), array($this, "gc") ); } public function open() { echo "open<br/>"; $this->_lifetime = ini_get('session.gc_maxlifetime'); return TRUE; } public function read($id) { echo "read--{$id}<br/>"; $data = $this->_memcache->get($id); return $data; } public function write($id, $data) { echo "write--{$id},--{$data}<br/>"; $ttl = 1200; //var_dump($data); $result = $this->_memcache->replace($id, $data, 0, $ttl); if($result === FALSE) { $result = $this->_memcache->add($id, $data, 0, $ttl); } return $result; } public function destroy($id) { echo "destory--{$id}<br/>"; return $this->_memcache->delete($id); } public function gc() { echo "gc<br/>"; return TRUE; } public function close() { echo "close<br/>"; return TRUE; } public function __destruct() { //session_write_close(); } } new Cache_Session_Handler(); session_start();
自己实现的session的功能,借用session_start产生cookie,也可以手动生成session_id自己控制cookie。
<?php Class Usession { public static $userdata; public static $destruct = true; public $memObj; public $session_name; public $session_id; public $sess_match_ip=false; public $sess_match_useragent=false; public $now; public $config_session = array( 'cookie_prefix' => "", 'cookie_domain' => ".mypharma.com", 'cookie_path' => "/", 'cookie_secure' => FALSE, 'cookie_httponly' => FALSE, 'sess_cookie_name' => 'mphsess', 'sess_expiration' => 0, 'sess_expire_on_close' => true, 'sess_memcached_ttl' => 864000, 'sess_match_ip' => TRUE, 'sess_match_useragent' => TRUE, ); private static $_instance; protected $_memcache_conf = array( 'default' => array( 'default_host' => '127.0.0.1', 'default_port' => 11211, 'default_weight' => 1 ) ); public static function getInstance($params = array()) { if(! (self::$_instance instanceof self) ) { self::$_instance = new self($params); } return self::$_instance; } private function __clone() { } private function __construct($params = array()) { include_once __DIR__."/config_memcached.php"; $this->set_config($config_session,$_memcache_conf); $this->now = time(); $this->mem_create(); $this->session_name = $config_session['sess_cookie_name'].'_id'; $this->session_id = isset($_COOKIE[$this->session_name]) ? $_COOKIE[$this->session_name] : null; if(empty($params)){ $this->sess_create(); } } public function set_config($config_session,$_memcache_conf) { if(!empty($config_session) AND is_array($config_session)) { foreach($config_session as $k => $v){ $this->config_session[$k] = $v; } } if(!empty($_memcache_conf) AND is_array($_memcache_conf)) { $this->_memcache_conf = null; foreach($_memcache_conf as $k => $v){ $this->_memcache_conf[$k] = $v; } } } public function set_session_id($session_id) { $this->session_id = $session_id; //$this->set_session_cookie(true,true); } public function sess_check() { // Is the session data we unserialized an array with the correct format? if ( ! is_array(self::$userdata) OR ! isset(self::$userdata['session_id']) OR ! isset(self::$userdata['ip_address']) OR ! isset(self::$userdata['user_agent']) OR ! isset(self::$userdata['last_activity'])) { $this->sess_destroy(); return FALSE; } // Does the IP Match? if ($this->sess_match_ip == TRUE AND self::$userdata['ip_address'] != $this->ip_address()) { $this->sess_destroy(); return FALSE; } // Does the User Agent Match? if ($this->sess_match_useragent == TRUE AND trim(self::$userdata['user_agent']) != trim(substr($this->user_agent(), 0, 120))) { $this->sess_destroy(); return FALSE; } return TRUE; } public function set_session_cookie($flag=true,$isset=false) { $secure = (bool) $this->config_session['cookie_secure']; $http_only = (bool) $this->config_session['cookie_httponly']; if ($this->config_session['sess_expiration'] !== FALSE) { $expire = ($this->config_session['sess_expire_on_close'] == true) ? 0 : $this->config_session['sess_expiration']+time(); } if ($this->config_session['cookie_path']) { $path = $this->config_session['cookie_path']; } if ($this->config_session['cookie_domain']) { $domain = $this->config_session['cookie_domain']; } $cookieA = array( 'name' => $this->session_name, 'value' => $isset ? $this->session_id:uniqid("mph_",true), 'expire' => $flag ? $expire : time()-3600, 'domain' => $domain, 'path' => $path, 'secure' => $secure, 'httponly'=>$http_only, ); $this->set_cookie($cookieA); $this->session_id=$cookieA['value']; } public function sess_read() { self::$userdata = json_decode($this->memObj->get($this->session_id),true); } // -------------------------------------------------------------------- /** * Write the session data * * @access public * @return void */ public function sess_write($array) { foreach($array as $k => $v){ self::$userdata[$k]=$v; } $data = json_encode(self::$userdata); if (get_class($this->memObj) === 'Memcached') { return $this->memObj->set($this->session_id, $data, $this->config_session['sess_memcached_ttl']); } elseif (get_class($this->memObj) === 'Memcache') { return $this->memObj->set($this->session_id, $data, 0, $this->config_session['sess_memcached_ttl']); } return FALSE; } // -------------------------------------------------------------------- /** * Create a new session * * @access public * @return void */ public function sess_create() { if($this->session_id == null) { $this->set_session_cookie(); $this->init_data(); } $this->sess_read(); if($this->sess_check() === false){ $this->set_session_cookie(); $this->init_data(); } } // -------------------------------------------------------------------- /** * Destroy the current session * * @access public * @return void */ public function sess_destroy() { self::$userdata = array(); self::$destruct = false; $this->memObj->delete($this->session_id); return $this->set_session_cookie(false); } public function userdata($key='') { if(empty($key)){ return self::$userdata; } if(isset(self::$userdata[$key])){ return self::$userdata[$key]; }else{ return null; } } public function set_userdata($key,$val='') { if(is_array($key)){ foreach($key as $kk => $vv){ self::$userdata[$kk]=$vv; } }else{ self::$userdata[$key]=$val; } } protected function init_data() { $array['session_id'] = $this->session_id; $array['ip_address'] = $this->ip_address(); $array['user_agent'] = substr($this->user_agent(), 0, 120); $array['last_activity'] = $this->now; $this->sess_write($array); } protected function mem_create() { if (class_exists('Memcached', FALSE)) { $this->memObj = new Memcached(); } elseif (class_exists('Memcache', FALSE)) { $this->memObj = new Memcache(); } else { log_message('error', 'Failed to create object for Memcached Cache; extension not loaded?'); return FALSE; } foreach ($this->_memcache_conf as $name => $cache_server) { if ( ! array_key_exists('hostname', $cache_server)) { $cache_server['hostname'] = $this->_memcache_conf['default']['default_host']; } if ( ! array_key_exists('port', $cache_server)) { $cache_server['port'] = $this->_memcache_conf['default']['default_port']; } if ( ! array_key_exists('weight', $cache_server)) { $cache_server['weight'] = $this->_memcache_conf['default']['default_weight']; } if (get_class($this->memObj) === 'Memcache') { // Third parameter is persistance and defaults to TRUE. $this->memObj->addServer( $cache_server['hostname'], $cache_server['port'], TRUE, $cache_server['weight'] ); } else { $this->memObj->addServer( $cache_server['hostname'], $cache_server['port'], $cache_server['weight'] ); } } return TRUE; } public function __destruct() { if(self::$destruct){ $data = json_encode(self::$userdata); if (get_class($this->memObj) === 'Memcached') { return $this->memObj->set($this->session_id, $data, $this->config_session['sess_memcached_ttl']); } elseif (get_class($this->memObj) === 'Memcache') { return $this->memObj->set($this->session_id, $data, 0, $this->config_session['sess_memcached_ttl']); } } } // -------------------------------------------------------------------- /** * Does nothing for native sessions * * @access public * @return void */ public function _sess_gc(){} public function ip_address() { $this->ip_address = $_SERVER['REMOTE_ADDR']; if ( ! $this->valid_ip($this->ip_address)) { $this->ip_address = '0.0.0.0'; } return $this->ip_address; } public function valid_ip($ip, $which = '') { $which = strtolower($which); // First check if filter_var is available if (is_callable('filter_var')) { switch ($which) { case 'ipv4': $flag = FILTER_FLAG_IPV4; break; case 'ipv6': $flag = FILTER_FLAG_IPV6; break; default: $flag = ''; break; } return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flag); } if ($which !== 'ipv6' && $which !== 'ipv4') { if (strpos($ip, ':') !== FALSE) { $which = 'ipv6'; } elseif (strpos($ip, '.') !== FALSE) { $which = 'ipv4'; } else { return FALSE; } } $func = '_valid_'.$which; return $this->$func($ip); } protected function _valid_ipv4($ip) { $ip_segments = explode('.', $ip); // Always 4 segments needed if (count($ip_segments) !== 4) { return FALSE; } // IP can not start with 0 if ($ip_segments[0][0] == '0') { return FALSE; } // Check each segment foreach ($ip_segments as $segment) { // IP segments must be digits and can not be // longer than 3 digits or greater then 255 if ($segment == '' OR preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3) { return FALSE; } } return TRUE; } // -------------------------------------------------------------------- /** * Validate IPv6 Address * * @access protected * @param string * @return bool */ protected function _valid_ipv6($str) { // 8 groups, separated by : // 0-ffff per group // one set of consecutive 0 groups can be collapsed to :: $groups = 8; $collapsed = FALSE; $chunks = array_filter( preg_split('/(:{1,2})/', $str, NULL, PREG_SPLIT_DELIM_CAPTURE) ); // Rule out easy nonsense if (current($chunks) == ':' OR end($chunks) == ':') { return FALSE; } // PHP supports IPv4-mapped IPv6 addresses, so we'll expect those as well if (strpos(end($chunks), '.') !== FALSE) { $ipv4 = array_pop($chunks); if ( ! $this->_valid_ipv4($ipv4)) { return FALSE; } $groups--; } while ($seg = array_pop($chunks)) { if ($seg[0] == ':') { if (--$groups == 0) { return FALSE; // too many groups } if (strlen($seg) > 2) { return FALSE; // long separator } if ($seg == '::') { if ($collapsed) { return FALSE; // multiple collapsed } $collapsed = TRUE; } } elseif (preg_match("/[^0-9a-f]/i", $seg) OR strlen($seg) > 4) { return FALSE; // invalid segment } } return $collapsed OR $groups == 1; } protected function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE) { if (is_array($name)) { // always leave 'name' in last place, as the loop will break otherwise, due to $$item foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'name') as $item) { if (isset($name[$item])) { $$item = $name[$item]; } } } if ($prefix == '' AND $this->config_session['cookie_prefix'] != '') { $prefix = $this->config_session['cookie_prefix']; } if ($domain == '' AND $this->config_session['cookie_domain'] != '') { $domain = $this->config_session['cookie_domain']; } if ($path == '/' AND $this->config_session['cookie_path'] != '/') { $path = $this->config_session['cookie_path']; } if ($secure == FALSE AND $this->config_session['cookie_secure'] != FALSE) { $secure = $this->config_session['cookie_secure']; } if ( ! is_numeric($expire)) { $expire = time() - 86500; } else { $expire = ($expire > 0) ? time() + $expire : 0; } setcookie($prefix.$name, $value, $expire, $path, $domain, $secure); } protected function user_agent() { return ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT']; } }
配置文件与上面的类文件同目录:
<?php $config_session['cookie_prefix'] = ""; $config_session['cookie_domain'] = ".mypharma.com"; $config_session['cookie_path'] = "/"; $config_session['cookie_secure'] = FALSE; $config_session['cookie_httponly'] = FALSE; $config_session['sess_cookie_name'] = 'mphsess'; $config_session['sess_expiration'] = 0; $config_session['sess_expire_on_close'] = true; $config_session['sess_memcached_ttl'] = 10*24*60*60; $config_session['sess_match_ip'] = TRUE; $config_session['sess_match_useragent'] = TRUE; $_memcache_conf = array( '23'=>array( 'hostname'=>'192.168.11.23', 'port'=>11211, 'weight'=>1 ) );