Session 的概念
Session 和 Cookie 一样,也是针对 HTTP 的局限性而提出的一种保持客户端和服务器端会话连接状态的机制。
Session 被称为会话,指用户在进入网站到浏览器关闭(或退出网站)这段时间内与 Web 系统的会话过程。
Session 的存储
Session 保存在服务器端,默认情况以文件的形式保存在服务器硬盘上,每个 Session 一个文件,文件名如:sess_j64kv3np0ft2u00aun0cilqdo2,里面保存的内容结构是:变量名 | 类型:长度:值;
例如:
name|s:3:"dee";level|i:2;
在 php.ini 中,session.save_handler 定义了存储和获取 Session 数据的处理器的名字,默认为 files,表示使用文件形式保存 Session:
session.save_handler = files
也可以设置成用户自定义方式存储 Session 的 user 或者 memcache 来使 Session 保存到关系型数据库或者 Memcache 等内存服务器中。
在 php.ini 中,session.save_path 表示 Session 存储的位置,在 WAMPSERVER 环境下的默认值是:
session.save_path = "d:/wamp/tmp"
表示 Session 文件存储在 d 盘 wamp/tmp 目录下; Windows 环境中在该配置被注释的情况下 Session 可能保存在 C:WindowsTemp 目录下,Linux 环境中在该配置被注释的情况下 Session 保存在 /tmp 或 /var/lib/php/session 目录下。
当 Session 保存在 Memcache 中时可以修改该配置,例如:
session.save_path = "tcp://127.0.0.1:11211,tcp://127.0.0.1:11212"
以上配置代表将 Session 保存到两台 Memcache 服务器中。
当 Session 以文件形式保存时,默认是保存在硬盘的一个目录下,当 Session 比较多时,磁盘读取文件会比较慢,通常一个目录下文件数量超过 2000 个时,读写这个目录就会比较慢。此时可以考虑将 Session 分目录存放。session.save_path 有一个可选的 N 参数来决定 Session 文件分布的目录深度,格式是:
session.save_path = "N;MODE;/path"
N 表示要设置的目录级数,MODE 表示目录的权限属性,默认为 600(Windows 中不需要设置),/path 表示 Session 文件存放的根目录路径每一级目录分别以 0 - 9 和 a - z 共 36 个字符作为目录名,这样存放 Session 的目录可以达到 36^36 个。创建文件夹的工作需要 PHP 来完成。
例如,2 级目录即 N = 2 时可以这样设置:
<?php //设置Session目录分级与保存路径 ini_set('session.save_path', "2;/wamp/tmp"); //创建2级目录 $string = '0123456789abcdefghijklmnopqrstuvwxyz'; $length = strlen($string); for($i = 0; $i < $length; $i++) { for($j = 0; $j < $length; $j++) { createfolder('d:/wamp/tmp/'.$string[$i].'/'.$string[$j]); } } function createfolder($path) { if(!@file_exists($path)) { createfolder(@dirname($path)); @mkdir($path, 0777); } } session_start(); $_SESSION['name'] = 'dee'; echo session_id();
打印出的 Session ID 为 3b9ptga94p7b5v1s33dnacrf95,那么 PHP 会根据该 ID 的前两位字符来存储 Session 文件,即该 Session 文件存储在 d:/wamp/tmp/3/b 下:
如果使用了 N 参数并且大于 0,那么 PHP 将不会自动回收过期的 Session,需要通过 shell 或者 cron 来清除过期的文件。
Session 的开启
通过 session_start 函数创建新会话或者重用现有会话,在 session_start 函数之前页面不能有任何的输出内容(当 php.ini 中 的output_buffering = Off 时)。
当自动开启会话或者通过 session_start() 手动开启会话的时候, PHP 内部会调用会话管理器(save_handler)的 open 、 read 和 gc 回调函数。
会话管理器默认为文件(file), 也可以是 SQLite 或者 Memcached 扩展, 还可以通过 session_set_save_handler() 设定的用户自定义会话管理器,并通过 read 回调函数返回现有的(经过序列化的)会话数据, PHP 会自动反序列化数据并且赋给 $_SESSION 超级全局变量。
Session ID 及其传递
Session 的唯一标识 Session ID,是由 32 位十六进制数组成的字符串,php.ini 中 session.use_cookies 的默认值为 1,即默认通过 Cookie 来进行保存和传递:
session.use_cookies = 1
通过设置也能在客户端禁用了 Cookie 之后通过 URL 或者隐藏表单进行 session_id 的 GET 或 POST 传递。
如果需要在 Cookie 被禁用时仍然能使用 Session,需要修改 php.ini 中的几个配置:session.use_only_cookies 和 session.use_trans_sid,这两个参数的默认值分别是 1 和 0,分别代表在客户端仅仅使用 Cookie 来存放 Session ID ,和指定不启用透明 Session ID 支持(即在 URL 中不自动加上 Session ID 的参数):
session.use_only_cookies = 1
session.use_trans_sid = 0
需要把这两个配置修改为:
session.use_only_cookies = 0
session.use_trans_sid = 1
此时如果禁用了 Cookie,则 URL 的格式为 格式为 a.php?PHPSESSID=session_id
将 session_id 通过 URL 进行传递的缺点有:1. 安全性,将 session_id 暴露在了 URL 中 2. 当禁用了 Cookie 后,如果 URL 中没有传递 session_id(例如重复访问第一个设置 Session 的页面),就会使系统不能重用现有会话而重新生成新的会话。
例 在 FireFox 40.0.3 下,设置禁用 Cookie:
”工具“ -- ”选项“ -- ”隐私“ -- ”使用自定义历史记录设置“ -- 取消勾选 ”接收来自站点的Cookie“
session_url_set.php:
<?php header("Content-type:text/html;charset=utf-8"); ini_set('session.use_trans_sid', 1); ini_set('session.use_only_cookies', 0); session_start(); $_SESSION['user'] = 'dee'; echo session_id(); echo '<pre>'; print_r($_SESSION); echo '<a href="session_url_get.php" target="_blank">跳转</a>';
输出:
session_url_get.php
<?php ini_set('session.use_trans_sid', 1); ini_set('session.use_only_cookies', 0); session_start(); echo session_id(); echo '<pre>'; print_r($_SESSION);
输出:
session_id 自动成为 URL 参数。
但是此时再次刷新 session_url_set.php 时,会重新生成新的 Session:
当用户向服务器服务器发出请求时,服务器会首先检查请求中是否已经包含了 Session ID(通过请求头中 Cookie 中的 PHPSESSID 或者 URL 中的参数 PHPSESSID ),如果包含则服务器会重复使用该 Session ID,如果不包含,则生成一个新的 Session ID。
注意:URL 自动带上 Session ID 参数的条件是 ① 当前 Cookie 中不包含 Session ID ;② session.use_trans_sid 设置为 1 ;③ session.use_only_cookies 设置为 0
可以通过修改 php.ini 中的 session.name 来修改 Seeesion Name,默认这是 PHPSESSID:
session.name = PHPSESSID
可以通过在 PHP 脚本中输入 session_name() 来查看当点的 Session Name :
echo session_name();
可以通过 session_id() 来查看当前的 Session ID:
echo session_id();
输出的值和对应的 Session 文件名相对应,例如 Session ID 是 j64kv3np0ft2u00aun0cilqdo2,文件名是 sess_j64kv3np0ft2u00aun0cilqdo2。
注销变量和销毁 Session
Session 的注销包括 4 个步骤:
<?php //1.开启 Session session_start(); //2.删除所有的 Session 变量 $_SESSION = array(); //3.如果是基于 Cookie 的 Session,则删除该 Cookie if(isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time() - 1, '/'); } //4.彻底销毁 Session session_destroy();
此时 保存 Session ID 的 Cookie已经被清除,Session 文件的内容已经被清空,但是服务器上的 Session 文件的删除则需要依赖 Session 的垃圾回收机制。
Session 的生存周期和垃圾回收机制
默认情况下,Session ID 存储在 Cookie 中,该 Cookie 默认设置过期时间为 0,即随着浏览器关闭而消失(Cookie 保存在内存中),php.ini 的设置:
session.cookie_lifetime = 0
关闭浏览器后该 Session ID 自动注销,如果重新请求该页面则会重新注册一个新的 Session ID。
但是 Session 文件可能仍然存在,原因是 Session 的垃圾回收机制。和 Session 的垃圾回收机制相关的函数是:session_start(),在调用该函数时启动回收机制,相关的 3 个配置分别是 :session.gc_maxlifetime,表示 Session 文件的过期时间,默认为 1440 秒即 24分钟能、session.gc_probability、session.gc_divisor,后面两个配置的默认值分别是 1 和 1000,代表 session_start() 函数每调用 1000 次触发一次 Session 文件的全部扫描,把过期的 Session 文件删除:
session.gc_maxlifetime = 1440 session.gc_probability = 1 session.gc_divisor = 1000
过期的 Session 文件的判断标准是:文件的修改时间和当前时间相差是否大于 session.gc_maxlifetime。当用户每进行一个操作哪怕是一个刷新页面的动作时,都会修改 Session 文件的修改时间。
可以手动设置 Session 的生命周期:
$life_time = 3600 * 24;
setcookie(session_name(), session_id(), time() + $life_time, '/');
经过设置,在关闭浏览器后,保存 Session ID 的 Cookie 的生命周期达到了 24 小时,也就是在 24 小时内即使关闭了浏览器,下次再访问该页面时 PHP 会重用该 Session。
可以通过设置较短的 session.gc_maxlifetime 和 较小的 session.gc_divisor 来观察 Session 的过期机制:
把 session.gc_maxlifetime 设为 10(秒),即 10 秒内用户没有任何操作(包括刷新)的话,10 秒后该会话即成为过期 Session;把 session.gc_divisor 分别设为 1 和 2,也就是 session.gc_probability / session.gc_divisor = 1 或 1/2,意思是每一 或 两次访问网站(页面),都将扫描所有的 Session 文件,同时将过期的 Session 文件从硬盘上删除(当 Session 以 files 形式存储时),来观察 Session 的垃圾回收机制。注意 session.cookie_lifetime 要和 session.gc_maxlifetime 设置为相同的值。
session_cookie_set.php
<?php header("Content-type:text/html;charset=utf-8"); ini_set('session.cookie_lifetime', 10); ini_set('session.gc_maxlifetime', 10); ini_set('session.gc_divisor', 1); session_start(); $_SESSION['username'] = 'deathmask'; echo session_id(); echo '<pre>'; print_r($_SESSION); echo '<a href = "session_cookie_get.php" target="_blank">跳转</a>';
session_cookie_get.php
<?php ini_set('session.cookie_lifetime', 10); ini_set('session.gc_maxlifetime', 10); ini_set('session.gc_divisor', 1); session_start(); echo session_id(); echo '<pre>'; print_r($_SESSION);
观察硬盘上 Session 文件的删除情况。
参考: