您的位置:晶晶的博客>PHP>PHP中的session略解

PHP中的session略解

在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理论中的过程可以看出来,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_probabilitysession.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

转载请注明本文标题和链接:《PHP中的session略解

相关推荐

哟嚯,本文评论功能关闭啦~

  1. #1

    很全面的session详解。

    雷雪松 7年前 (2017-02-21) 回复
  2. #2

    一脸蒙逼的进来,一脸蒙逼的出去……

    文栋说自媒体 7年前 (2017-01-21) 回复