Session 的原理及最佳实践
2018-03-26 22:16
351 查看
Http协议是基于请求和响应的一种无状态的协议,而通过session可以使得Http应用变得有状态,即可以“记住”客户端的信息。今天就来说说这个session和cookie。
那么服务器是如何获取这些浏览器的cookie的呢?是通过超全局变量
那么服务器是如何记住众多用户的会话数据呢?首先要将客户端和服务端建立一一对应关系,使得每个客户端都要一个唯一标示,这样,服务器才能识别出来。建立唯一标识的方法是通过cookie或者通过GET方式在服务端建立session。php在使用session的时候,默认的会建立一个名叫”PHPSESSID”的cookie,值是唯一的(这个名字可以通过php.ini修改session.name值去改,这个唯一值的生成方式也可以修改),并在某个文件(session.save_path目录)下保存一个文件(文件名唯一由刚刚生成的那个PHPSESSID cookie决定),然后发送给客户端,客户端在再次发送请求的时候,就会把这个名叫”PHPSESSID”的cookie带过来,也就是
php会根据这个
4000
执行
需要注意的是:
PHPSESSID这个名称是可以配置的
ession保存的位置也是可以配置的通过php.ini中session.save_path设置,甚至,可以通过别的方式保存在数据库或者缓存中
在存session时限序列化,读取的时候,先反序列化
这就是session的实现机制和原理。在机制不变的情况下,每个环节的实现方式几乎都可以自定义。比如可以在这些方面实现自定义:
如果客户端不支持cookie,你也可以通过GET方式将session id发送到服务端
如果你想改变唯一session_id的生成方式,你也可以选择用uniqid
你也可以改变session存放的路径
可以改变session文件的前缀,后缀
甚至你可以把session存在数据库,缓存当中
因此,session的使用方式灵活多样,前提是你了解这个实现机制。大部分上面的DIY都可以在php.ini中通过配置实现。下面我们介绍下如何实现将session保存在除文件以外别的媒介中,毕竟,文件也只是一种而已。
可以通过实现这个接口,来自定义session的存贮方式,比如数据库。当然,要实现一些基本的方法。
比如自己来重新写一个:
如果是缓存(如memcached或者redis),就应该持有一个Cache或者Redis实例,并委托这个实例来实现open/close/read/write功能,整体不复杂,重在规范。
至于这个gc值得介绍下,为啥要有个gc?因为session总要有个过期或者失效时间,因此启用一个garbage collection机制,也就是几率性回收机制,。但是问题又来了,为啥不每次都去检测下当前过期的session并除之而后快呢?这里涉及到一个性能问题,对session读写操作是频繁的,如果每次都要在session操作时在所有的session当中检测下哪些过期,哪些没有过期,是不是太低效了点?尤其是session保存在文件中时尤其如此,一个session就是一个文件,客户连接数一多,文件的数量将非常巨大。因此不用每次检查,而是按照一定几率去回收,这样性能就不至于收到太大影响,在足够多次数访问后,绝大多数过期session也能得到清理。
那么,这又牵扯进另一个问题——什么是过期的session?
超过这个gc_maxlifetime时间,session会被认为是garbage,有垃圾就有垃圾回收,但是垃圾回收的检查却不是每次都进行,而是按照一个几率,分别是这两个参数:
那么,每次请求,垃圾会被回收的概率就是
所以,在服务端方面,session主要和
把这个的值设置为1,利用cookie来传递sessionid;
这个代表SessionID在客户端Cookie储存的时间,默认是0,代表浏览器一关闭sessionid就作废。如果想使得PHPSESSID cookie永久有效,这个可以设为一个很大的值如999999999。另外也可以通过
使用方法:
为了防止并发的写session,任何时刻只能允许有一个PHP脚本在操作session,因此,一个脚本一旦
因此,最佳实践是,任何session变量,数据的更改(如
下面介绍一个有可能在单点登录中会用到的例子:
Session 的原理
session是在服务器端保持用户会话数据的一种方法,对应的cookie是在客户端保持用户数据。为了在客户端(比如浏览器)可以跨页面交流数据,Netscape将cookie引入浏览器。所以,cookie是保存在浏览器端的。那么服务器是如何获取这些浏览器的cookie的呢?是通过超全局变量
$_COOKIE。就是说,服务端在接到请求(request)的时候通过这个
$_COOKIE获取客户端的数据,而在响应(respsonse)的时候又通过
$_COOKIE这个变量把数据传给客户端;对于客户端,通过
$_COOKIE将自己的数据发送给服务端,而又通过
$_COOKIE获取服务端方面想要传递的值。这是服务端和客户端交流数据cookie的方式。这里说的客户端,可以是浏览器,也可以是别的;相同的浏览器,不同的网页发的请求都属于统一客户端;不同浏览器发的相同请求属于不同的客户端。
那么服务器是如何记住众多用户的会话数据呢?首先要将客户端和服务端建立一一对应关系,使得每个客户端都要一个唯一标示,这样,服务器才能识别出来。建立唯一标识的方法是通过cookie或者通过GET方式在服务端建立session。php在使用session的时候,默认的会建立一个名叫”PHPSESSID”的cookie,值是唯一的(这个名字可以通过php.ini修改session.name值去改,这个唯一值的生成方式也可以修改),并在某个文件(session.save_path目录)下保存一个文件(文件名唯一由刚刚生成的那个PHPSESSID cookie决定),然后发送给客户端,客户端在再次发送请求的时候,就会把这个名叫”PHPSESSID”的cookie带过来,也就是
$_COOKIE["PHPSESSID"],这个cookie的值不是session本身,而是一个session_id,一个和客户端一一对应的id。
php会根据这个
session_id去找对应的session文件。比如:
sess_vv9lpgf0nmkurgvkba1vbvj915,这里的
vv9lpgf0nmkurgvkba1vbvj915就是一个
session_id。这个文件和客户端也是一一对应的。这里面保存的值就是对应这个客户端在服务端所保存的数据。
使用Session之前为什么必须先执行session_start()?
了解的原理之后,所谓的session其实就是客户端一个session id服务器端一个session file。新建session之前4000
执行
session_start()是告诉服务器要种一个
cookie($_COOKIE["PHPSESSID"])以及准备好对应的session文件,然后再发送出去,是谁发过来的请求,谁就会获得这个唯一的标示。读取session之前执行session_start()是告诉服务器,赶紧根据从客户端发过来的
$_COOKIE中去获取
$_COOKIE["PHPSESSID"]这个session_id,然后去某个路径下获取由
session_id决定的唯一的文件(
sess_vv9lpgf0nmkurgvkba1vbvj915),再把值读取出来。
需要注意的是:
PHPSESSID这个名称是可以配置的
ession保存的位置也是可以配置的通过php.ini中session.save_path设置,甚至,可以通过别的方式保存在数据库或者缓存中
在存session时限序列化,读取的时候,先反序列化
这就是session的实现机制和原理。在机制不变的情况下,每个环节的实现方式几乎都可以自定义。比如可以在这些方面实现自定义:
如果客户端不支持cookie,你也可以通过GET方式将session id发送到服务端
如果你想改变唯一session_id的生成方式,你也可以选择用uniqid
你也可以改变session存放的路径
可以改变session文件的前缀,后缀
甚至你可以把session存在数据库,缓存当中
因此,session的使用方式灵活多样,前提是你了解这个实现机制。大部分上面的DIY都可以在php.ini中通过配置实现。下面我们介绍下如何实现将session保存在除文件以外别的媒介中,毕竟,文件也只是一种而已。
自定义存储
要想将session保存在别的媒介(比如缓存)需要先介绍一个接口:SessionHandlerInterface
可以通过实现这个接口,来自定义session的存贮方式,比如数据库。当然,要实现一些基本的方法。
SessionHandlerInterface { /* 方法 */ abstract public bool close ( void ) abstract public bool destroy ( string $session_id ) abstract public bool gc ( string $maxlifetime ) abstract public bool open ( string $save_path , string $name ) abstract public string read ( string $session_id ) abstract public bool write ( string $session_id , string $session_data ) }
比如自己来重新写一个:
<?php class MySessionHandler implements SessionHandlerInterface { private $savePath; public function open($savePath, $sessionName) { $this->savePath = $savePath; if (!is_dir($this->savePath)) { mkdir($this->savePath, 0777); } return true; } public function close() { return true; } public function read($id) { return (string)@file_get_contents("$this->savePath/sess_$id"); } public function write($id, $data) { return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true; } public function destroy($id) { $file = "$this->savePath/sess_$id"; if (file_exists($file)) { unlink($file); } return true; } public function gc($maxlifetime) { foreach (glob("$this->savePath/sess_*") as $file) { if (filemtime($file) + $maxlifetime < time() && file_exists($file)) { unlink($file); } } return true; } } $handler = new MySessionHandler(); session_set_save_handler($handler, true); session_start();
如果是缓存(如memcached或者redis),就应该持有一个Cache或者Redis实例,并委托这个实例来实现open/close/read/write功能,整体不复杂,重在规范。
至于这个gc值得介绍下,为啥要有个gc?因为session总要有个过期或者失效时间,因此启用一个garbage collection机制,也就是几率性回收机制,。但是问题又来了,为啥不每次都去检测下当前过期的session并除之而后快呢?这里涉及到一个性能问题,对session读写操作是频繁的,如果每次都要在session操作时在所有的session当中检测下哪些过期,哪些没有过期,是不是太低效了点?尤其是session保存在文件中时尤其如此,一个session就是一个文件,客户连接数一多,文件的数量将非常巨大。因此不用每次检查,而是按照一定几率去回收,这样性能就不至于收到太大影响,在足够多次数访问后,绝大多数过期session也能得到清理。
那么,这又牵扯进另一个问题——什么是过期的session?
Session的过期问题
在php中设置session有很多方面,包括给session设置值或直接设置过期、失效和有效期。服务端方面
在PHP中主要通过设置session.gc_maxlifetime来设定Session的生存周期:ini_set('session.gc_maxlifetime', 3600); //设置时间 ini_get('session.gc_maxlifetime');//得到ini中设定值
超过这个gc_maxlifetime时间,session会被认为是garbage,有垃圾就有垃圾回收,但是垃圾回收的检查却不是每次都进行,而是按照一个几率,分别是这两个参数:
session.gc_probability = 1 session.gc_divisor = 1000
那么,每次请求,垃圾会被回收的概率就是
session.gc_probability / session.gc_divisor = 1/1000
所以,在服务端方面,session主要和
session.gc_maxlifetime有关,次要和
session.gc_probability及
session.gc_divisor有关;
客户端方面
主要和cookie的过期时间有关。php.ini中通过
session.name = PHPSESSID来保存session的cookie名字默认为
PHPSESSID(可以修改),那么可以设定这个cookie的过期时间来实现session的过期。
session.use_cookies = 1;
把这个的值设置为1,利用cookie来传递sessionid;
session.cookie_lifetime = 0
这个代表SessionID在客户端Cookie储存的时间,默认是0,代表浏览器一关闭sessionid就作废。如果想使得PHPSESSID cookie永久有效,这个可以设为一个很大的值如999999999。另外也可以通过
session_set_cookie_params()函数来设定PHPSESSID cookie的有效期
结论
不管是设置Session的为N秒过期还是永不过期,都要同时设定session.gc_maxlifetime和
session.cookie_lifetime为同一值N;永不过期时可以设定为一个非常大的值(如9999999)
设置session过期的例子
有个设置session过期时间的例子可以参考下:function start_session($expire = 0) { if ($expire == 0) { $expire = ini_get('session.gc_maxlifetime'); } else { ini_set('session.gc_maxlifetime', $expire); } if (empty($_COOKIE['PHPSESSID'])) { session_set_cookie_params($expire); session_start(); } else { session_start(); setcookie('PHPSESSID', session_id(), time() + $expire); } }
使用方法:
//600秒以后过期 start_session(600);
最佳实践
session_start()会创建新会话或者重用现有会话,与之相反
session_commit()/session_write_close()即保存当前session数据,并且关闭当前会话。前者是open+read,后者是write+close,由此可见
session_commit的重要性了吧?
为了防止并发的写session,任何时刻只能允许有一个PHP脚本在操作session,因此,一个脚本一旦
session_start打开session,那么在此脚本终止或者调用
session_write_close()之前,别的任何脚本都不能使用session。在默认情况下脚本结束时会自动写入和关闭session,但是在脚本执行时间比较长的时候,此脚本就一致占据锁使得别的脚本无法使用session,因此导致许多错误。
因此,最佳实践是,任何session变量,数据的更改(如
$_SESSION[xx] = xxx),都要及时使用
session_commit()保存数据,关闭会话。
下面介绍一个有可能在单点登录中会用到的例子:
//从数据库中获取先前登录的session_id $pre_sid = $user_session['session_id']; //当前登录的session_id $now_sid = session_id(); //先将当前会话置为$pre_sid session_id($pre_sid); //open+read之前的会话数据 session_start(); //unset()掉之前会话数据 session_unset(); //write+close之前的会话数据 session_commit(); //再将会话设置成当前的 session_id($now_sid); //open+read当前的会话数据 session_start(); $_SESSION['username'] = $username; //write+close当前的会话数据 session_commit();
相关文章推荐
- Session 的原理江苏快三开奖网出售及最佳实践
- atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7
- OSGi原理与最佳实践:第一章 OSGi框架简介(2)
- (转)SVN分支/合并原理及最佳实践
- WWDC 2016 Session笔记 - Session 716 Core Location最佳实践(Core Location Best Practices)
- SVN分支/合并原理及最佳实践
- WWDC 2016 Session笔记 - Session 213 使用最佳实践改进App(Improving Existing Apps with Modern Best Practices)
- SVN分支/合并原理及最佳实践
- SVN分支/合并原理及最佳实践
- 《深入理解OSGi:Equinox原理、应用与最佳实践》笔记_2_建立开发环境
- 《深入java虚拟机--JVM高级特性与最佳实践》学习笔记(三) GC原理与垃圾收集器
- 软件需求最佳实践——SERU过程框架原理与应用(典藏版)
- 《深入理解OSGi:Equinox原理、应用与最佳实践》笔记_2_建立开发环境
- SVN分支/合并原理及最佳实践2
- 学习笔记TF061:分布式TensorFlow,分布式原理、最佳实践
- SVN分支/合并原理及最佳实践
- SVN分支/合并原理及最佳实践
- SVN分支/合并原理及最佳实践
- 《深入理解OSGi:Equinox原理、应用与最佳实践》笔记_1_运行最简单的bundlehelloworld
- 《大话存储 网络存储系统原理精解与最佳实践》