在web编程领域,session是啥已经说烂了,既然是略解,就略微歪解一下下:http是无状态的,为了在服务器端能够识别当前访问者的“状态”,引入了session的机制,通过http请求中某种标识符来识别、标记当前访问者,不至于因为http无状态、同时有多个用户访问服务器端时搞的服务器端傻傻分不清到底谁是谁;或者某一个用户5分钟前访问过首页,5分钟后由首页进入到其他页面时服务器端傻傻记不住这个用户是不是以前访问过。session就是用来解决这一类问题,在php编程中一般将session(以及cookie)有关的内容称之为:会话控制。
PHP中session基础
1、白话session理论
session会话管理中最基础的原理就是为每个访客分配一个独一无二的标识,用这个独一无二的标识来区分不同的用户,每个访客每次访问的时候必须带上这个独一无二的标识,服务器就能依据这个独一无二的标识来区别不同的访客,也就是说携带不同的标识就是不同的用户,当然对一直携带同一个标识的访客而言就可以为他设定各种“状态”了,例如保持登录状态、为这个用户分配独一无二的变量。在session理论中这个独一无二的标识被称之为会话ID,PHP中就是那个大名鼎鼎的session_id
了,当然这里的独一无二仅仅是对单台服务器下的单个域名而言,并不是全球互联网上独一无二。
那么对于客户端浏览器来说如何做到每次访问的时候都自动带上这个独一无二的标识呢?基于http协议,要实现每次浏览器发送http请求访问咱们的网站的时候都附带一个独一无二的标识,只有两种方案:一种是在url中将session_id
附带在get(或post)变量中,另外一种就是将session_id
保存在cookie中;前者因为存在潜在的session_id
泄露风险和问题,现在使用的比较少了;php中默认的使用的是客户端浏览器的cookie,因为同域下的cookie在每次访问新页面时浏览器会自动附带这些cookie发送http请求,那么服务器就能自动接收到这个包含有session_id
的特殊cookie了。当然这里的“特殊”并不是保存这个session_id
的cookie与其他cookie有差别,其实这个cookie与普通的cookie毫无二致,只不过在php中处理这个cookie实现session机制是由php内部自动处理的,不需要咱们在php代码中去直接操纵这个“特殊”的cookie,所以某些地方又会有session cookie
这种说法。
对服务器端来说,区分不同的session_id
标记的访客也是需要在服务器端存储这些session_id
变量以及不同的session_id
代表的访客各不相同的变量的,这样才能实现不同用户的不同的“状态”标记。PHP接收session_id
并存储和管理session_id
本身以及session_id
下的变量是有一套自己的内部机制的,当然这套内部机制各位PHPer也是可以自由定制的,只不过php内部默认提供一套基于文件系统的服务器端session
管理机制,咱们并未过多关注罢了。
2、session初始化
php中要使用session第一步就是初始化session,也就是bool session_start ([ array $options = [] ] )
函数,一般session_start()
函数无需传参,函数内部会自动使用默认参数,而默认参数由php.ini
配置文件所决定;$options
是一个关联数组,数组key与php.ini
文件中的相关配置项相互关联,只不过不需要session.
前缀,例如在php.ini
中的配置项session.save_handler
,对应$options
中的key就为save_handler
,初始化session可以做很多事儿,包括但不限于:设定session的会话管理器(与调用session_set_save_handler
函数设定session
会话管理器类似)、设定session
在客户端的实现方式(前述的url中附带session_id
变量或使用特殊的cookie存储session_id
变量)等等。
session_start(); //使用文件系统存储 session_start(['name'=>'JJonline','save_handler'=>'files','save_path'=>'/tmp/','use_cookies'=>1]); //使用redis存储,当然需php环境支持redis扩展 session_start(['name'=>'JJ','save_handler'=>'redis','save_path'=>'tcp://127.0.0.1:6379','use_cookies'=>1]);
session初始化在一个页面中仅需也仅能调用一次,php面向过程和面向对象的混杂编程的方式导致了session初始化可以在全局代码中调用一次,也可以在不同的单独页面中单独调用;这就导致了代码量庞大的时候傻傻不知道到底初始化session没有。php提供有一个判断session状态的函数int session_status ( void )
,这个函数返回三种由php内置常量标记的int型数字,至于数字是几不重要,知道这3个常量的名称就可以了,分别是:PHP_SESSION_DISABLED
会话是被禁用的、PHP_SESSION_NONE
会话是启用的,但不存在当前会话、PHP_SESSION_ACTIVE
会话是启用的,而且存在当前会话,第一种状态只在session扩展被禁用的情况下才会出现,这种情况session压根都不可用,所以健壮一点儿的session初始化代码如下:
if(PHP_SESSION_ACTIVE != session_status()) { // ini_set('session.auto_start', 0); //这里可有可无 session_start(); }
2、session的使用
session的使用其实就是对超全局数组变量$_SESSION
的灵活应用,那什么是超全局变量呢?也就是php中开箱即可使用的那些在全局作用域均可使用的内置变量,白话来说就是你写的php代码中任意位置均可直接使用这些变量而无需初始化(实际上这个变量的初始化是有的,只不过由php内部自动完成,由session_start()
函数启动;也就是说调用完session_start()
函数后就可以使用$_SESSION
变量了),既然是变量那就可以赋值、修改以及销毁了;session值的赋值、销毁与普通变量一致。
// 设置session值,仅当前session_id下下可用不影响其他session_id对用的访客 $_SESSION['name'] = 'value';//字符串方式赋值 $_SESSION['user'] = ['id'=>1,'name'=>'Jing'];//数组方式赋值 // 删除销毁session值 $_SESSION['name'] = null; unset($_SESSION['user']); // 或者全部销毁 session_destroy(); // 判断session值是否存在 if(isset($_SESSION['name'])) { // code ... }
另外需要注意的一点是:由于php默认用于客户端保存session_id使用的是客户端cookie,而服务器端发送cookie时要在输出内容之前,所以session_start()
函数一定要在输入内容之前以及使用超全局数组变量$_SESSION
之前,否则可能会报如下错误:
Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent
PHP中自定义session会话管理器提升性能
php中默认提供的基于文件系统的session会话管理器;在网站高并发或者应用到分布式session时,这个基于文件系统的会话管理器的性能就捉襟见肘了。下图是我测试session时php默认的基于文件的会话管理器产生的一些session临时文件,会话管理器会有一定的垃圾回收机制,废弃的session数据将在一定概率下被删除。
由白话session理论中的过程可以看出来,session初始化的时候要做一些事儿了:
1、设定php内部用于存储、处理session的管理器或者说管理机制的一些参数(是否自动开启session.auto_start
、存储手段session.save_handler
、存储路径session.save_path
等);
2、设定用于客户端存储session_id
的特殊cookie的一些属性,例如:这个cookie的名字(session.name
)、这个特殊cookie本身的一些特性如过期时间(session.cookie_lifetime
)、作用域(session.cookie_domain
)、作用路径(session.cookie_path
)、是否仅https下可用(session.cookie_secure
)、以及是否http生成的cookie可用(session.cookie_httponly
)等于cookie属性有关的东西;
3、既然客户端保存session_id
的特殊cookie有过期时间,那么服务器端存储所有访客的session_id相关的数据,也必然也有各种属性的设定,如:过期时间、过期的session相关数据占用的资源回收机制等等。
自定义会话管理器
php中自定义会话管理器,也就是按php的会话管理器逻辑来定义一套回调函数,然后使用bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )
将这些回调函数注册启用即可。
会话管理器调用的逻辑如下:
会话开始的时候,PHP会自动调用open
回调函数执行一些管理器必要的初始化(例如默认的基于文件系统的session打开文件句柄、设置文件锁之类的动作),然后再调用read
回调函数来读取某个session_id下的内容,read回调函数返回经过编码的字符串。 然后PHP会自动将这个字符串解码,并且产生一个数组对象,自动赋值保存至$_SESSION
超级全局变量中,所以php代码中可以直接使用$_SESSION
超级全局变量,无需理会这个编码、解码的过程;如果想设定或了解这个编码、解码的过程,可以参考session.serialize_handler
相关的内容。
当 PHP 关闭的时候(或者调用了 session_write_close()
之后), PHP 会对$_SESSION
中的数据进行编码, 然后和会话ID一起传送给write
回调函数并执行。 write
回调函数调用完毕之后,PHP内部将调用close
回调函数用于执行一些管理器必要的析构动作(也就是销毁动作,比如基于文件系统的session关闭文件句柄、解锁文件锁之类的动作)。
销毁会话时,PHP会自动调用destroy
回调函数。
根据会话生命周期时间的设置,PHP会不时地调用gc
回调函数。 该函数会从持久化存储中删除超时的会话数据。 超时是指会话最后一次访问时间距离当前时间超过了$lifetime
所指定的值。
这7个回调函数的参数和含义分别如下:
bool open(string $savePath, string $sessionName)
,$savePath
参数由php.ini文件中session.save_path
配置项,或者session_start
传递的对应值所决定,也就是一个路径;$sessionName
由php.ini文件中session.name
配置项,或者session_name()
函数的参数,或者session_start
传递的对应值所决定,也就是用于客户端保存session_id的那个cookie的名称,一般默认为PHPSESSID
。这个函数需实现一些你自己定义的会话管理器的初始化动作,返回布尔值,你的初始化逻辑成功true,初始化逻辑失败false,下方不再赘述。
bool close(void)
,这个回调函数相当于一个类中的析构函数,用于你自定义的会话管理器的一些收尾工作,返回布尔值。
string read(string $sessionId)
读回调和写回调是相互协调的,你实现的读逻辑需返回字符串,有数据按write传给你的数据原样返回,没有数据返回空字符串。
bool write(string $sessionId, string $data)
,$sessionId
即session_id值,$data
即需要写入的session数据,写入时你可以修改这个数据,但务必保证在read函数中将你做的修改给还原回来。
bool destroy(string $sessionId)
,$sessionId
即session_id值,也就是销毁该session_id下的所有数据。
bool gc(int $lifetime)
,为了清理会话中的失效无用的数据,PHP会自动不时的调用gc这个回调函数。 调用周期由 session.gc_probability
和session.gc_divisor
参数控制。 传入到此回调函数的$lifetime
参数由 session.gc_maxlifetime
设置,返回布尔值。
string create_sid(void)
,当需要新的会话ID时被自动调用的回调函数。 回调函数被调用时无传入参数, 其返回值应是一个字符串格式的、有效的、全局唯一的会话ID。 php内部已实现了一个比较高效的session_id生成器,这个参数是可选的,建议没有合适随机数发生器时不予实现。
自php5.4起,session_set_save_handler
还有一种面向对象的形式:bool session_set_save_handler ( SessionHandlerInterface $sessionhandler [, bool $register_shutdown = true ] )
,实现SessionHandlerInterface
接口并实例化后传递给session_set_save_handler
函数即可。
接口的概要如下:
SessionHandlerInterface { abstract public bool close ( void ) abstract public bool destroy ( string $session_id ) abstract public bool gc ( int $maxlifetime ) abstract public bool open ( string $save_path , string $session_name ) abstract public string read ( string $session_id ) abstract public bool write ( string $session_id , string $session_data ) }
SessionHandlerInterface
接口内部需实现的方法与session_set_save_handler
第一种形式的各个回函数非常一致,就不多赘述了。需要注意的是php官方提供有一个SessionHandlerInterface
接口的实现示例SessionHandler
,但SessionHandler
仅仅是一个示例,在实现自己的回话管理器时不宜继承SessionHandler
类,最佳的方式是实现SessionHandlerInterface
接口。
缘由是在关于SessionHandler
类的帮助文档下方有个警告,蹩脚英语就不多说了。如下:
This class is designed to expose the current internal PHP session save handler, if you want to write your own custom save handlers, please implement the SessionHandlerInterface interface instead of extending from SessionHandler.
https://php.net/manual/zh/class.sessionhandler.php
很全面的session详解。