PHP Session和Cookie,Session阻塞,Session垃圾回收
什么是 Session
在 web 应用开发中,Session 被称为会话。主要被用于保存某个访问者的数据。
由于 HTTP 无状态的特点,服务端是不会记住客户端的,对服务端来说,每一个请求都是全新的。
既然如此,那么服务端怎么知道是哪个访问者在请求它呢?又如何将不同的数据对应上正确的访问者?答案是,给访问者一个唯一获取 Session 中数据的身份标识。
打个比方:当我们去超市购物时,被保安告之我们是不能带物品进去的,必须将物品寄放在超市的储物箱中。我们把物品交给了他,他怎么知道这些物品谁是谁的,于是他给了我们不同的钥匙。当我们要取走我们的物品时,用唯一的钥匙打开对应的箱子即可。
就如同上面的比方一样,可以将 Session 理解为存放我们数据的“箱子”,当然,这些“箱子”都在服务端那。服务器给访问者唯一的“钥匙”,这个“钥匙”被称作 session_id。访问者凭借自己的 session_id,就能获取到自己存在服务器端的数据。
session_id 通过两种方式传给访问者(客户端):URL 或 Cookie。详情参见:传送会话ID
Session 和 Cookie 有什么关系
Cookie 也是由于 HTTP 无状态的特点而产生的技术。也被用于保存访问者的身份标识和一些数据。每次客户端发起 HTTP 请求时,会将 Cookie 数据加到 HTTP header 中,提交给服务端。这样服务端就可以根据 Cookie 的内容知道访问者的信息了。
可以说,Session 和 Cookie 做着相似的事情,只是 Session 是将数据保存在服务端,通过客户端提交来的 session_id 来获取对应的数据;而 Cookie 是将数据保存在客户端,每次发起请求时将数据提交给服务端的。
上面提到,session_id 可以通过 URL 或 cookie 来传递,由于 URL 的方式比 cookie 的方式更加不安全且使用不方便,所以一般是采用 cookie 来传递 session_id。
服务端生成 session_id,通过 HTTP 报文发送给客户端(比如浏览器),客户端收到后按指示创建保存着 session_id 的 cookie。cookie 是以 key/value 形式保存的,看上去大概就这个样子的:
PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2
在 PHP 中,保存 session_id 的 cookie 名称默认叫作 PHPSESSID,这个名称可以通过 php.ini 中 session.name 来修改,也可以通过函数 session_name() 来修改。
为什么不推荐使用 PHP 自带的 files 型 Session 处理器
在 PHP 中,默认的 Session 处理器是 files,处理器可以用户自己实现(参见:自定义会话管理器)。我知道的成熟的 Session 处理器还有很多:Redis、Memcached、MongoDB……为什么不推荐使用 PHP 自带的 files 类型处理器,PHP 官方手册中给出过这样一段 Note:
无论是通过调用函数 session_start() 手动开启会话, 还是使用配置项 session.auto_start 自动开启会话, 对于基于文件的会话数据保存(PHP 的默认行为)而言, 在会话开始的时候都会给会话数据文件加锁, 直到 PHP 脚本执行完毕或者显式调用 session_write_close() 来保存会话数据。 在此期间,其他脚本不可以访问同一个会话数据文件。
上述引用参见:Session 的基本用法
为了证明这段话,我们创建一下 2 个文件:
文件:session1.php
1 |
<?php |
2 |
session_start(); |
3 |
sleep(5); |
4 |
var_dump( $_SESSION ); |
5 |
?> |
文件:session2.php
1 |
<?php |
2 |
session_start(); |
3 |
var_dump( $_SESSION ); |
4 |
?> |
在同一个浏览器中,先访问 http://127.0.0.1/session1.php,然后在当前浏览器新的标签页立刻访问 http://127.0.0.1/session2.php。实验发现,session1.php 等了 5 秒钟才有输出,而 session2.php 也等到了将近 5 秒才有输出。而单独访问 session2.php 是秒开的。在一个浏览器中访问 session1.php,然后立刻在另外一个浏览器中访问 session2.php。结果是 session1.php 等待 5 秒钟有输出,而 session2.php 是秒开的。
分析一下造成这个现象的原因:上面例子中,默认使用 Cookie 来传递 session_id,而且 Cookie 的作用域是相同。这样,在同一个浏览器中访问这 2 个地址,提交给服务器的 session_id 就是相同的(这样才能标记访问者,这是我们期望的效果)。当访问 session1.php 时,PHP 根据提交的 session_id,在服务器保存 Session 文件的路径(默认为 /tmp,通过 php.ini 中的 session.save_path 或者函数 session_save_path() 来修改)中找到了对应的 Session 文件,并对其加锁。如果不显式调用 session_write_close(),那么直到当前 PHP 脚本执行完毕才会释放文件锁。如果在脚本中有比较耗时的操作(比如例子中的 sleep(5)),那么另一个持有相同 session_id 的请求由于文件被锁,所以只能被迫等待,于是就发生了请求阻塞的情况。
既然如此,在使用完 Session 后,立刻显示调用 session_write_close() 是不是就解决问题了哩?比如上面例子中,在 sleep(5) 前面调用 session_write_close()。
确实,这样 session2.php 就不会被 session1.php 所阻塞。但是,显示调用了 session_write_close() 就意味着将数据写到文件中并结束当前会话。那么,在后面代码中要使用 Session 时,必须重新调用 session_start()。
例如:
01 |
<?php |
02 |
session_start(); |
03 |
$_SESSION [ "name"
|