session可以说是当前互联网提到的最多的名词之一了。它的含义很宽泛,可以指任何一次完整的事务交互(会话):如发送一次HTTP请求并接受响应,执行一条SQL语句都可以看做一次Session。如无特殊说明,本文中提到的Session单指HTTP会话。
本文是PHP内核探索的第五篇,主要包含如下几个方面的内容:
- 背景知识和session基础
- PHP中session的原理
- 参考文献
一、背景知识,session基础
1. HTTP是无状态的
我们知道,HTTP协议最初是匿名的、无状态的请求/响应协议。这样简单的设计可以使HTTP协议专注于资源的传输(HTTP是超文本传输协议),从而获得较好的性能。但这种无状态的设计也验证阻碍了交互web应用的发展,典型的如:电商网站需要获取用户的信息,以实现订单、购物车、交易等功能,SNS网站需要获取用户信息并存档,以建立真正的“社交网络”,甚至电影和CD租赁网站,也需要获取用户信息,以提供个性化的推荐,从而带来更好的效益。这意味着,必须要使用某种技术来识别和管理用户信息,Cookie和Session技术便是在这种背景下诞生的。
2. Session与Cookie
说到Session,就不得不提Session的好基友Cookie,因为很多情况下Session依赖于Cookie存储其session_id。而如果要说Session和Cookie的区别,我想大家应该都不陌生,有的同学甚至可以轻松背出如下一些常见的区别:
(1). Cookie是客户端保持状态的解决方案,而Session是服务器端保持状态的技术,因此,Cookie是存储在客户端的,而Session是存储在服务器端的。
(2). 大多数情况下,Session需要使用Cookie做载体,来存放session_id,所以,如果禁用了Cookie,必须要通过其他的手段来获取这个session_id( 例如通过get或者post的方式将session_id传递给服务器 )
(3). Cookie过期和删除只能保证客户端的连接的失效,并不会清除服务器端的Session
(4). 尽管默认情况下,Session和Cookie都是写文件的( Session也可以写数据库或者其他内存缓存如memcached ),但是,Cookie则依赖于浏览器的设定:例如,IE6下限定每个域名下最多20个Cookie,很多浏览器限制Cookie的大小不能超过4096字节。
关于Cookie的更多讨论,已经超出了本文的范畴,需要了解的同学可以参考《HTTP权威指南》和《JavaScript高级程序设计》这两本书,相信一定会对Cookie有更加深入的理解。
3. php中Session的基本操作
php中,Session相关的操作是以扩展的形式提供的 ( 源码目录:PHPSRC/ext/session/ )。PHP提供了大量的、丰富的API来操作Session:
(1). session_start
bool session_start ( void )
session_start()用于启动一个会话,一般而言,我们在使用$_SESSION时,都要先调用session_start( 或者你的php.ini中配置了session.auto_start )。那么在session.auto_start=false的情况下, session_start是不是一定是session操作的第一个必须调用的函数呢?答案是否定的。虽然在一般情况下,我们在需要操作session时,基本上都是将session_start()放在脚本的第一行,但实际上在调用session_start时,Session相关的参数都已经初始化完毕,这之后是无法通过session_name和session_set_cookie_params, session_save_path等函数更改Session的参数信息的。所以,如果需要更改session的相关参数,除了可以在ini文件中更改(或者通过ini_set更改),还可以通过session_name, session_save_path, session_set_cookie_params等函数修改,且这些函数必须在session_start之前调用。例如:
session_save_path('/root/xiaoq/phpCode/session');
session_start();
$_SESSION['index'] = "this is desc";
$_SESSION['int'] = 123;
session_start()调用之后,除了要设置Session的基本参数之外,还会以一定的概率启动Session的GC。
(2). session_id()
如同数据库中每条记录需要一个主键一样,Session也需要一个id值用于唯一标识一个Client,这个标识便是session_id。函数session_id()用于设置或者更改当前会话的session_id,例如:
session_save_path('/root/xiaoq/phpCode/session');
session_start();
$_SESSION['index'] = "this is desc";
$_SESSION['int'] = 123;
print_r( session_id());//5rdhbe4k8k73h5g1fsii01iau5
在设置了session.save_handler=files的情况下,服务器端是以sess_{session_id}的命名方式来储存Session数据文件的:
正常情况下,不同会话的session_id是不会重复的。在已知session_id的情况下,我们可以通过传递session_id的方法来获取Session数据,从而避开Cookie的限制:
session_save_path('/root/xiaoq/phpCode/session');
session_id("5rdhbe4k8k73h5g1fsii01iau5");
session_start();
print_r($_SESSION);
/* Array
(
[index] => this is desc
[int] => 123
) */
Session文件存储会有很多问题和瓶颈,关于这一点,之后也会有详细的说明和解释。
(4). session_write_close/session_commit
默认情况下,session数据是在当前会话结束时(一般就是指脚本执行完毕时)才会写入文件的,这样会带来一些问题。例如,如果当前脚本执行过长,那么当其他脚本访问同一session_id下的session数据时便会阻塞(这实际上会涉及到文件锁flock,之后会有说明),直到前一脚本执行完毕并写入session文件。可以用sleep来简单模拟这一情况:
session_save_path('/root/xiaoq/phpCode/session');
session_start();
$_SESSION['index'] = "this is desc";
$_SESSION['int'] = 123;
sleep(15);
避免这一情况的一种方法是:在session数据使用完毕之后,调用session_commit或者session_write_close函数通知服务器写入session数据并关闭文件(释放flock的锁):
session_save_path('/root/xiaoq/phpCode/session');
session_start();
$_SESSION['index'] = "this is desc";
$_SESSION['int'] = 123;
session_commit();
sleep(15);
注意session_commit和session_write_close只是同一函数的不同别名。
(5). session_destroy
很多同学在会话结束的时候,都是通过unset($_SESSION)的方式来删除会话数据(这与session_unset()的作用类似)。实际上这样并不是稳妥的做法,原因是:unset($_S