Heim  >  Artikel  >  php教程  >  [转]PHP5 session 详解

[转]PHP5 session 详解

WBOY
WBOYOriginal
2016-06-06 19:45:01841Durchsuche

来源:http://blog.163.com/lgh_2002/blog/static/4401752620105246517509/ http协议是WEB服务器与客户 端(浏览器)相互通信的协议,它是一种无状态协议。所谓无状态,指的是不会维护http请求数据,http请求是独立的,非持久的。而越来越复杂的WEB 应用,需要

来源:http://blog.163.com/lgh_2002/blog/static/4401752620105246517509/

 

http协议是WEB服务器与客户 端(浏览器)相互通信的协议,它是一种无状态协议。所谓无状态,指的是不会维护http请求数据,http请求是独立的,非持久的。而越来越复杂的WEB 应用,需要保存一些用户状态信息。这时候,Session这种方案应需而生。PHP从4.1开始支持Session管理。  

session是很抽象的一个概念。我们不妨先从与它几个息息相关的有迹可寻的小切入点入手,然后逐渐地认识了解它。

session存储  

首先,我们为什么需要Session,就是因为我们需要存储各个用户的状态数据。那么试问,如果由你来设计解决这个需求的方案,那么也许你会设置这样一个数据表用与存储各个用户的状态信息:

                                         

uid created data max_age
94c55770fdf044a7 1270802787 jtUsername=admin 14400
2c37df64277e4409 1270822787 jtUsername=Joe;jtBooks=8; 14400

 

 

uid : 用户唯一标识符,区分其它用户

created : 记录产生时间

data : 存放与用户相关的数据

max_age : 记录的有效时间  

同样地,PHP设计管理session方案也大致如此,它分别包含了以下信息:  

1. session id       用户session唯一标识符,随机生成的一串字符串,具有唯一性,随机性。主要用于区分其它用户的session数据。用户第一次访问web页面的时候,php的session初始化函数调用会分配给当前来访用户一个唯一的ID,也称之为session_id。

2. session data       我们把需要通过session保存的用户状态信息,称为用户session数据,也称为session数据。

3. session file       PHP 默认将session数据存放在一个文件里。我们把存放session数据的文件称为session文件。它由特殊的php.ini设置 session.save_path指定session文件的存放路径,CentOS5.3操作系统,PHP5.1默认存放在/var/lib/php /session目录中。用户session文件的名称,就是以sess_为前缀,以session_id为结尾命名,比如session id为vp8lfqnskjvsiilcp1c4l484d3,那么session文件名就是 sess_vp8lfqnskjvsiilcp1c4l484d3

4. session lifetime       我们把初始化session开始,直到注销session这段期间,称为session生命周期,这样有助于我们理解session管理函数。  

由 此,我们可见: 当每个用户访问web, PHP的session初始化函数都会给当前来访用户分配一个唯一的session ID。并且在session生命周期结束的时候,将用户在此周期产生的session数据持久到session文件中。用户再次访问的时 候,session初始化函数,又会从session文件中读取session数据,开始新的session生命周期。  

与session存储相关php.ini设置

1. session.save_handler = file 用于读取/回写session数据的方式,默认是files。它会让PHP的session管理函数使用指定的文本文件存储session数据

2. session.save_path =“/var/lib/php/session” 指 定保存session文件的目录,可以指定到别的目录,但是指定目录必须要有httpd守护进程属主(比如apache或www等)写权限,否则无法回存 session数据。当指定目录不存在时,php session环境初始化函数是不会帮你创建指定目录的,所以需要你手工建立指定目录。 它还可以写成这样session.save_path =“N;/path” 其中N是整数。这样使得不是所有的session文件都保存在同一个目录中,而是分散在不同目录。这对于服务器处理大量session文件是很有帮助的。(注:目录需要自己手工创建)

3. session.auto_start = 0 如果启用该选项,用户的每次请求都会初始化session。我们推荐不启用该设置,最好通过session_start()显示地初始化session。

Session同步数据  

一 旦调用了session_start()初始化session,就意味着开始了一个session生命周期。也就是宣布了,可以使用相关函数操 作$_SESSION来管理session数据。这个session生命周期产生的数据并没有实时地写入session文件,而是通过$_SESSION 变量寄存在内存中。那么,寄存在内存的数据什么时候会写入到session文件?这也是我们这一小节的主要测试内容。  

在进行测试之前,先让我们介绍几个影响session数据的PHP函数、或事件

1. session_start()       函数session_start会初始化session,也标识着session生命周期的开始。要使用session,必须初始化一个session环境。有点类似于OOP概念中调用构造函数构创建对象实例一样。 session 初始化操作,声明一个全局数组$_SESSION,映射寄存在内存的session数据。如果session文件已经存在,并且保存有session数 据,session_start()则会读取session数据,填入$_SESSION中,开始一个新的session生命周期。

2. $_SESSION       它是一个全局变量,类型是Array,映射了session生命周期的session数据,寄存在内存中。在session初始化的时候,从session文件中读取数据,填入该变量中。在session生命周期结束时,将$_SESSION数据写回session文件。

3. session_register()       在 session生命周期内,使用全局变量名称将注全局变量注册到当前session中。所谓注册,就是将变量填入$_SESSION中,值为NULL。它 不会对session文件进行任何IO操作,只是影响$_SESSION变量。注意,它的正确写法是 session_register(‘varname’),而不是session_register($varname)

4. session_unregister()       与session_register操作正好相反,即在session生命周期,从当前session注销指定变量。同样只影响$_SESSION,并不进行任何IO操作。

5. session_unset()       在 session生命周期,从当前session中注销全部session数据,让$_SESSION成为一个空数组。它与 unset($_SESSION)的区别在于:unset直接删除$_SESSION变量,释放内存资源;另一个区别在 于,session_unset()仅在session生命周期能够操作$_SESSION数组,而unset()则在整个页面(page)生命周期都能 操作$_SESSION数组。session_unset()同样不进行任何IO操作,只影响$_SESSION数组。

6. session_destroy()       如果说session_start()初始化一个session的话,而它则注销一个session。意味着session生命周期结束了。在 session生命周期结整后,session_register, session_unset, session_register都将不能操作$_SESSION数组,而$_SESSION数组依然可以被unset()等函数操作。这 时,session意味着是未定义的,而$_SESSION依然是一个全局变量,他们脱离了关映射关系。 通过session_destroy()注销session,除了结束session生命周期外,它还会删除sesion文件,但不会影响当前$_SESSION变量。即它会产生一个IO操作。

7. session_regenerate_id()       调 用它,会给当前用户重新分配一个新的session id。并且在结束当前页面生命周期的时候,将当前session数据写入session文件。前提是,调用此函数之前,当前session生命周期没有被 终止(参考第9点)。它会产生一个IO操作,创建一个新的session文件,创建新的session文件的是在session结束之前,而不是调用此函 数就立即创建新的session文件。

8. session_commit()       session_commit() 函数是session_write_close()函数的别名。它会结束当前session的生命周期,并且将session数据立即强制写入 session文件。不推荐通过session_commit()来手工写入session数据,因为PHP会在页面生命周期结束的时候,自动结束当前没 有终止的session生命周期。它会产生一个IO写操作

9. end session       结 束session,默认是在页面生命周期结束的之前,PHP会自动结束当前没有终止的session。但是还可以通过session_commit()与 session_destroy()二个函数提前结束session。不管是哪种方式,结束session都会产生IO操作,分别不一样。默认情况,产生 一个IO写操作,将当前session数据写回session文件。session_commit()则是调用该函数那刻,产生一个IO写操作,将 session数据写回session文件。而session_destroy()不一样在于,它不会将数据写回session文件,而是直接删除当前 session文件。有趣的是,不管是session_commit(),还是session_destroy()都不会清空$_SESSION数组,更 不会删除$_SESSION数组,只是所有session_*函数不能再操作session数据,因为当前的session生命周期终止了,即不能操作一 个未定义对象。

为了验证以上陈述,我们可以做以下测试       任务1: 观察session初始化与默认结束session的时候,产生的IO操作

 

<span>//@file test_session_2.php<br>session_start();<br>$pg_uuid = 'ac606826-9620-490b-b850-ea9dbce6cfd5'; <br>//注册全局变量pg_uuid到session,但$_SESSION['pg_uuid']值为NULL,只影响$_SESSION<br>session_register('pg_uuid');<br>var_dump($_SESSION);<br>fopen(__FILE__, "r");</span>
<span>[root@localhost ~]# strace -p `cat /var/run/httpd.pid`
</span>
 Process 21819 attached - interrupt to quit
<span>...<br>st_mode=S_IFREG|0644, st_size=72, ...}) = 0<br>open("/var/www/html/test_session.php", O_RDONLY) = 17<br>fstat64(17, {st_mode=S_IFREG|0644, st_size=72, ...}) = 0<br>lseek(17, 0, SEEK_CUR)                  = 0<br>read(17, "<p test_session.php>read(17, "", 8192)                      = 0<br>read(17, "", 8192)                      = 0<br>close(17)                               = 0<br>gettimeofday({1270906664, 11602}, NULL) = 0<br>open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17<br>flock(17, LOCK_EX)                      = 0<br>fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0<br>fstat64(17, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0<br>time(NULL)                              = 1270906664<br>open("/var/www/html/test_session.php", O_RDONLY) = 18<br>fstat64(18, {st_mode=S_IFREG|0644, st_size=72, ...}) = 0<br>lseek(18, 0, SEEK_CUR)                  = 0<br>close(18)                               = 0<br>chdir("/var/lib/php/session")           = 0<br>pwrite64(17, "", 0, 0)                  = 0<br>close(17)                               = 0<br>setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0<br>writev(16, [{"HTTP/1.1 200 OK\r\nDate: Sat, 10 A"..., 385}], 1) = 385<br>write(12, "192.168.0.98 - - [10/Apr/2010:21"..., 207) = 207<br>shutdown(16, 1 /* send */)              = 0<br>epoll_wait(15,</p></span>
<span>      </span><span>蓝色加粗,通过系统内核函数open调用打开session文件,这是由session_start()产生的调用, </span><span>      </span><span>注意这里并没有产生读文件操作。红色部分,将一个空字符串写入session文件。 </span><span>      </span><span>由此可见session初始化在页面生命周期开始之时,手工调用session_start可以初始化session文件, </span><span>      </span><span>而在页面生命周期结束之时,会自动地注销session,结束当前session生命周期, </span><span>      </span><span>同时在此周期产生的session数据写回session文件,我们把这种方式结束的session,称为session默认结束。 </span>

任务2观察session_register()查看它是否会产生磁盘操作,还是只操作$_SESSION。

 

//@file test_session_2.php

session_start();

$pg_uuid = 'ac606826-9620-490b-b850-ea9dbce6cfd5';

session_register('pg_uuid'); //注册全局变量pg_uuid到session,但值为NULL,只影响$_SESSION

var_dump($_SESSION);

fopen(__FILE__, "r");

 

[root@localhost ~]# strace -p `cat /var/run/httpd.pid`

Process 21819 attached - interrupt to quit

...

open("/var/www/html/test_session_2.php", O_RDONLY) = 17

fstat64(17, {st_mode=S_IFREG|0644, st_size=148, ...}) = 0

lseek(17, 0, SEEK_CUR)                  = 0

read(17, "

read(17, "", 8192)                      = 0

read(17, "", 8192)                      = 0

close(17)                               = 0

open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17

flock(17, LOCK_EX)                      = 0

fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0

fstat64(17, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0

time(NULL)                              = 1270907613

open("/var/www/html/test_session_2.php", O_RDONLY) = 18

fstat64(18, {st_mode=S_IFREG|0644, st_size=148, ...}) = 0

lseek(18, 0, SEEK_CUR)                  = 0

close(18)                               = 0

chdir("/var/lib/php/session")           = 0

pwrite64(17, "pg_uuid|N;", 10, 0)       = 10

close(17)                               = 0

setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0

writev(16, [{"HTTP/1.1 200 OK\r\nDate: Sat, 10 A"..., 328}, {"array(1) {\n  [\"pg_uuid\"]=>\n  NUL"..., 36}], 2) = 364

write(12, "192.168.0.98 - - [10/Apr/2010:21"..., 210) = 210

shutdown(16, 1 /* send */)              = 0

epoll_wait(15,

 

通 过上面的观察,蓝色部分还是由session初始化(session_start)产生,注意这里依然没读文件操作,这是因为session文件为空。红 色部分,依然是默认结束session产生的文件写操作(pwrite)。由此,我们可以知道session_register()不会对session 文件操作,即不会把$_SESSION中的数据写回session文件,它没有产生任何IO操作。而只在session生命周期是影响当 前$_SESSION变量,即$_SESSION[‘pg_uuid’] = NULL。所以,推荐使用$_SESSION[‘pg_uuid’] = $pg_uuid;  

任务3观察session_destroy()与session_unset()的区别

 

session_start();

echo "
---1--
";

$pg_uid = 1;

//$_SESSION['pg_uid']; //该行会报一个Notice消息,即没有初始化该变量

$_SESSION['pg_name'] = 'boys'; //填入到$_SESSION变量,但不立即写入session文件,值为boys

$pg_sex = 1;

$pg_theme = 'default';

session_register('pg_sex'); //填入到$_SESSION变量,但不立即写入session文件,值为NULL

session_register('pg_theme'); //填入到$_SESSION变量,但不立即写入session文件,值为NULL

var_dump($_SESSION);

 

//--

echo "
---2--
";

unset($_SESSION['pg_theme']); //从$_SESSION清除该元素,不立即同步到session文件

unset($_SESSION['pg_name']); //从$_SESSION清除该元素,不立即同步到session文件

session_unregister('pg_sex'); //从$_SESSION清除该元素,不立即同步到session文件

session_unregister('pg_uid'); //从$_SESSION清除该元素,不立即同步到session文件

var_dump($_SESSION);

 

echo "
---3--
";

$_SESSION['pg_members'] = 5; //填入$_SESSION数组,但不立即同步到session文件,值为5

$pg_boy = 6;

session_register('pg_boy'); //填入$_SESSION数组,但不立即同步到session文件,值为NULL

session_unset($_SESSION); //清空$_SESSION

var_dump($_SESSION);

 

echo "
---4--
";

$_SESSION['pg_boss'] = 3; //填入$_SESSION数组,但不立即同步到session文件,值为3

$pg_girls = 6;

session_register('pg_girls'); //填入$_session数组,但不立即同步到session文件,值为NULL

session_destroy(); //注销session_destroy

var_dump($_SESSION);

 

echo "
---5---
";

session_unregister('pg_boss'); //pg_boss不会被清除,还为NULL

session_unset(); //不会清空$_SESSION数组,因为session已被session_destroy注销

var_dump($_SESSION);

 

fopen(__FILE__, "r");

   

//@这里是页面析构的时候-- 本应该将$_SESSION数据同步到session文件, 真的吗???

//@事实,没有发生任何IO操作,即没有将$_SESSION数据回写,怎么回事???

//@因为被session_destroy()消毁了session...

 

程序输出:

 ---1--

array(3) { ["pg_name"]=> string(4) "boys" ["pg_sex"]=> NULL ["pg_theme"]=> NULL }

---2--

array(0) { }

---3--

array(0) { }

---4--

array(2) { ["pg_boss"]=> int(3) ["pg_girls"]=> NULL }

---5---

array(2) { ["pg_boss"]=> int(3) ["pg_girls"]=> NULL }

   

[root@localhost ~]# strace -p `cat /var/run/httpd.pid`

 Process 21819 attached - interrupt to quit

...

open("/var/www/html/test_session_3.php", O_RDONLY) = 17

fstat64(17, {st_mode=S_IFREG|0644, st_size=706, ...}) = 0

lseek(17, 0, SEEK_CUR)                  = 0

read(17, "

read(17, "", 8192)                      = 0

read(17, "", 8192)                      = 0

close(17)                               = 0

open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17

flock(17, LOCK_EX)                      = 0

fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0

fstat64(17, {st_mode=S_IFREG|0600, st_size=10, ...}) = 0

pread64(17, "pg_uuid|N;", 10, 0)        = 10

close(17)                               = 0

unlink("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5") = 0

time(NULL)                              = 1270910665

open("/var/www/html/test_session_3.php", O_RDONLY) = 17

fstat64(17, {st_mode=S_IFREG|0644, st_size=706, ...}) = 0

lseek(17, 0, SEEK_CUR)                  = 0

close(17)                               = 0

chdir("/var/lib/php/session")           = 0

setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0

...

write(12, "192.168.0.98 - - [10/Apr/2010:22"..., 211) = 211

shutdown(16, 1 /* send */)              = 0

 

蓝 色部分是我们熟悉的session初始化的时候产生的open系统内核调用。绿色部分,是一个IO读操作,因为上一次访问页面的时候,产生了 session数据,所以这一次会将上次的session填入$_SESSION中。红色部分,可以看出,这里调用unlink删除session文件, 而且后面(页面生命周期结束时),一直没有看到前两例看到的任何与session文件有关的IO写操作,即没有将$_SESSION中的数据写回 session文件。我们也没有在session.save_path找到相应的session文件

<span>[root@localhost html]# ls /var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5  ls: /var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5: No such file or directory</span>

 

注意: 虽然删除了session文件,但用户再次访问web的时候,并不会给用户重新分配一个新的session id,而是依然用该session id,并且会重新创建文件名相同的session文件,即sess_SESSION-ID  

任务4测试并观察session_regenerate_id行为,以及$_SESSION的变化

<span>session_start();
$_SESSION['pfid'] = 123;
var_dump($_SESSION);
session_regenerate_id();
var_dump($_SESSION);
fopen(__FILE__, "r");</span>
<span>[root@localhost ~]# strace -p `cat /var/run/httpd.pid` </span>
Process 22641 attached - interrupt to quit
<span>...
open("/var/www/html/test_session_4.php", O_RDONLY) = 17
fstat64(17, {st_mode=S_IFREG|0644, st_size=141, ...}) = 0
lseek(17, 0, SEEK_CUR)                  = 0
read(17, "<?php \nsession_start();\n$_SESSION"..., 8192) = 141
read(17, "", 8192)                      = 0
read(17, "", 8192)                      = 0
close(17)                               = 0
open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17
flock(17, LOCK_EX)                      = 0
fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0
fstat64(17, {st_mode=S_IFREG|0600, st_size=11, ...}) = 0
pread64(17, "pfid|i:123;", 11, 0)       = 11
gettimeofday({1270915896, 122016}, NULL) = 0
time(NULL)                              = 1270915896
open("/var/www/html/test_session_4.php", O_RDONLY) = 18
fstat64(18, {st_mode=S_IFREG|0644, st_size=141, ...}) = 0
lseek(18, 0, SEEK_CUR)                  = 0
close(18)                               = 0
chdir("/var/lib/php/session")           = 0
close(17)                               = 0
open("/var/lib/php/session/sess_qoa6knu9fg77un8le99o1vk1c7", O_RDWR|O_CREAT, 0600) = 17
flock(17, LOCK_EX)                      = 0
fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0
pwrite64(17, "pfid|i:123;", 11, 0)      = 11
close(17)                               = 0
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
writev(16, [{"HTTP/1.1 200 OK\r\nDate: Sat, 10 A"..., 386}, {"array(1) {\n  [\"pfid\"]=>\n  int(12"..., 75}], 2) = 461
write(12, "192.168.0.98 - - [11/Apr/2010:00"..., 210) = 210
shutdown(16, 1 /* send */)              = 0</span>

 

观 察测试结果,蓝色部分照旧是session初始化的时候产生的系统内核open调用,接着绿色部分是一个IO读操作,即读取session文件中的数据, 由第一个var_dump($_SESSION)输出。随后,往session加入新的一条已定义了的session记录,并且通过 session_commit()将记录写回去。红色部分就是由session_commit产生的一次IO写操作。之 后,session_unset()并没有生效,同时,我们也没有在页面生命周期结束的时候看到任何与session文件有关的IO写操作。这也正说明 了,session_commit()调用的当下,就会将session数据写回session文件,并且会像session_destroy一样注销 session,但与session_destroy不同的时,session_commit不会删除session文件,而且会将当前的session 数据写回session文件。我们可以查看,调用session_commit之后,session文件还是依然存在的

<span>[root@localhost html]# ls -lt /var/lib/php/session </span>
<span> -rw------- 1 apache apache 31 Apr 11 03:18 sess_qoa6knu9fg77un8le99o1vk1c7  </span>
<span> -rw------- 1 apache apache 11 Apr 11 00:08 sess_4j38nv7l1fq1bj6n80l6g9cum5  …</span>

总结:  

1, 用户注销web应用系统,最好的调用方式依次是 session_unset();  session_destroy();  unset($_SESSION);

 
<span>function user_sigout() </span><span><span>{</span></span>
<span>  global $user;
  sys_event_register('user_sigout', $user);
 
  session_unset();			//清空session
  session_destroy();			//删除session文件
  if (isset($_SESSION)) {
    unset($_SESSIONI);	//注销$_SESSION
  }
  return TRUE;
}</span>

 

2, 尽量将键与值填入$_SESSION,而不推荐使用session_register()。同样,尽量使用unset($_SESSION[‘var’]),而不使用session_unregister()。

3, 对于可能产生大量session的WEB应用,推荐使用的session.save_path的格式是session.save_path=”N:/path”。注意:这些目录需要手工创建,并且有httpd守护进程属主写权限。这样做可以获得更好的性能

4, 如果调用了session_regenerate_id()给用户分配了新的session id。该函数并不会主动删除旧的session文件,需要定时清理旧的session文件,这样更优化。

5, 尽量不要使用session_commit()提交sessioin数据,因为它同时会结束当前session,PHP默认会在页面生命周期的时候提交session数据到session文件

 

Session ID传递

 

session 终究是因为管理用户状态信息才存在的。我们曾探讨过session id的意义:每个来访问用户都会被分配一个唯一的session id,用于区分其它用户的session数据。换句话说,session id是用户表明身份的一种标识,就像入场券一样。用户一旦从被分配了session id之后的每次访问(http请求)都会携带这个session id给服务端,用于加载该用户的session数据。那么,通过什么方式传给服务端?这是我们这节探讨的内容。  

用 户端与服务端的web通信协议是http。而PHP通过http取得用户数据惯用的三种方法分别是:POST方法、GET方法还有Cookie。而PHP 默认传递方法正是Cookie,也是最佳方法。只有在客户端不支持Cookie的时候(浏览器禁用了Cookie功能)才会通过GET方法来传递 session_id,即通过在URL的query_string部分传递session id。  

确 定了传递方法,我们还有必要清楚一下session id的传递过程。用户通过浏览器访问网页,将URL输入地址栏回车,浏览器发出请求,在调用sockect send之前浏览器引擎会搜索有效的Cookies记录封装在http请求头的Cookie字段,一同发送出去。服务端器接收到请求后,交给PHP处理。 这时,session初始化函数如果在$_COOKIE中没有找到以session_name()作为键值存储的生素(值为session id),则会以为用户是第一次访问web。作为第一次访问的用户,session初始化函数总会随机生成一个session_id并且通过 setcookie()函数调用将新生成的session_id以”sesseson_name = session_id”的格式填入http响应头Set-Cookie字段,发送给客户端(这样接下来的请求,http请求头Cookie字段都会携带该 Cookie记录给web服务器)。如果初始化函数发现用户端Cookies中已定义了存在$_COOKIE[‘sess_name’],则会加载 与$_COOKIE[‘sess_name’]相对应的session文件($_COOKIE[‘sess_name’]就是session ID)。如果用户Cookie记录过期,则会被浏览器删除。之后的下一次请求,服务器会以为用户又是第一次访问,如此循环。  

让我们通过测与来验证以上的陈述

 

 

//p1.php

session_start();

通过截取到的http数据包如下
#
T 192.168.0.98:2290 -GET /a.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1
; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Accept: application/xml,application/xhtml+
xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..Accept-Language: zh-CN,zh;
q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3….
##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Mon, 12 Apr 2010 08:25:11 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Set-Cookie
: PHPSESSID=bk7655dqrm5m884c9nitfi7j00; path=/..Expires: Thu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cach
e, must-revalidate, post-check=0, pre-check=0..Pragma: no-cache..Content-Length: 0..Connection: close..Content-Type: tex
t/html; charset=UTF-8….

 

第 一次访问/a.php的时候,请求包里面没有设置任何Cookie,所以这里的Cookie字段为空。当然服务器php也就得不到 的$_COOKIE[‘PHPSESSID’](即session id为空)。如此,服务器会以为用户是第一次访问web。所以session初始化的时候,会给用户分配一个唯一的session_id并且以 Cookie的方法传回给了用户端。  

我们再来观察第二次请求与响应,会有哪些变化:

 #
T 192.168.0.98:2314 -GET /a.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1
; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Cache-Control: max-age=0..Accept: applicat
ion/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..
Accept-Language: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cookie: PHPSESSID=bk7655dqrm5m884c9nitfi7j00..
..
##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Mon, 12 Apr 2010 08:32:13 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Expires: T
hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
cache..Content-Length: 0..Connection: close..Content-Type: text/html; charset=UTF-8

 

首 先,我们观察http请求,加红色部分是第一次http请求头没有出现的内容。我们可以看到,该Cookie正是第一次访问,服务端通过Set- Cookie要求浏览器设置的Cookie。它们是一样的,即session_id为bk7655dqrm5m884c9nitfi7j00。然后,我们 再观察这次的http响应,明显没有再要求用户端设置键为session_name()的Cookie了。  

我们再来测试伪造一个session_id发送给服务,观察服务端响应。我们写一个测试脚本,如下:

 
<span>$host = '192.168.0.8';
$port = 8080;
$path = '/p1.php';
$sid = "PHPSESSID=dk7655dqrm5m884c9nitfi7j00";
 
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
  fputs($fp, "GET {$path} HTTTP/1.1\r\n");
  fputs($fp, "Host: {$host}\r\n");
  fputs($fp, "Cookie: {$sid}\r\n");
  fputs($fp, "Connection: close\r\n\r\n");
  while (!feof($fp)) {
    $d .= fgets($fp, 4096);
  }
  fclose();
  echo $d;
 }</span>

 

抓到的http请求、响应数据包如下:

 #
T 192.168.0.98:2400 -GET /p1.php HTTTP/1.1..
Host: 192.168.0.8..Cookie: PHPSESSID=dk7655dqrm5m884c9nitfi7j00..Connection: close….
##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Mon, 12 Apr 2010 09:03:09 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Expires: T
hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
cache..Content-Length: 11..Connection: close..Content-Type: text/html; charset=UTF-8….hello world

      上面的session_id是用户端伪造的一个值,它并不实际存在。收到这样的请求,服务端并没有检查,而是以这个session_id创建了相应的 session文件。并且,从httpd响应头部信息来看,并没给用户端分配session id(没有Set-Cookie)。由此,我们可以推断:只要http请求头部包含了以session_name()作为键值的Cookie,那么服务端 就不认为用户是第一次访问web,亦不会给客户端分配session_id。否则,分配新的session_id,并通过Set-Cookie要求浏览器 创建该Cookie. 我们再来观察一下,通过session_regenerate_id()函数给用户分配一个新的session_id的情况

 

 

 

//@file: p2.php

session_start();

session_regenerate_id();

 

 

抓取到的http数据包如下

 ####
T 192.168.0.98:2763 -GET /p2.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.
1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Cache-Control: max-age=0..Accept: applica
tion/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch.
.Accept-Language: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cookie: PHPSESSID=bk7655dqrm5m884c9nitfi7j00.
… ##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Mon, 12 Apr 2010 11:39:10 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Expires: T
hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
cache..Set-Cookie: PHPSESSID=f7q6jfdug4ekfsjhop6jftgna7; path=/..Content-Length: 0..Connection: close..Content-Type: tex
t/html; charset=UTF-8…. 

 

## 上 面可以观察得到,http请求头Cookie部分带了session id,并且这个session_id还是用户第一次访问web时被分配得到的。这一次,http响应头跟第二次示例http响应有些不一样,而是像第一次 访问那样通过Set-Cookie去要求用户端浏览器更新用户的session id。这意味着:session_genrate_id()给用户端重新生成的session id也是通过Cookie的方法传递。

1,User01和User02第一次去访问/p1.php,分别被分配了一个session id。

2,User01和User02第二次访问web,都会使用由/p1.php分配的session_id

3,User01 因为访问了/p2.php,脚本/p2.php中的session_regenerate_id()给用户User01重新分配了一个新 session_id,从用户User01第4次访问的session_id就可以看得出来,与前面几几次的session_id不同了。

4,User02因为没有访问/p2.php,也就没有被服务端重新分配session id,一下沿用着上一次分配的session_id与session id传递的有关的php.ini设置

1,session.use_cookie = 1 是否采用Cookie方法传递session id值。默认是1,表示启用。

2,session.name = PHPSESSID 不 管是Cookie传递sessioin_id,还是GET方法传递session_id,都需要使用键值。他们的格式分别是Cookie:  sess_name=session_id;和/path.php?sess_name=session_id,其中sess_name就是由这里指定的

3,session.use_only_cookies = 0 表 示只使用Cookie 的方法传递session id。我们说过,传递cookie的方法,除了cookie,还有GET方法,GET方法是不安全的方法。在用户端禁用了cookie的时候,会采用 GET方法传递session_id,可以通过这个设置尽用GET方法传递session_id。

4,session.cookie_lifetime = 0, session.cookie_path = / 以及session.cookie_domain = 如 果使用Cookie方法传递session_id的话,这里分别指定了cookie有效域、目录和时间。分别对应setcookie()函数的形 参$expire、$path和$domain。其中cookie_lifetime=0表示直到关闭浏览器才删除Cookie。还可以使用 session_set_cookie_params()函数修改这些值。

5,session_name([string $name]) 获 取或更新session_name。如果传了name,则表示不使用默认的名称PHPSESSID(由session.name)指定,否则获取当前 session_name。注意:如果设置session_name,则必须在session_start()之前调用才生效。

6,session_id([string $id]) 与session_name()类似,但它是读取或者设置session_id的方法。同样,设置session_id的话,必须在session_start()之前调用才有效。

7,session_set_cookie_params()和session_get_cookie_params() 通 过session_set_cookie_params()可以重新设定session.cookie_lifetime, session.cookie_path以及session.cookie_domain这三个php.ini设置。而 session_get_cookie_params()则是获取这些设定的值。

Session回收  

通过上文几节介绍,我们知道session数据存放在服务端指定的session.save_path目录中,同时会在用户端存放一条Cookie用以记录分配给用户的session id。所以,session数据失效分服务端和客户端,要删除(回收)的对象也很清楚: 1,服务端:删除过期的session文件,启动PHP GC回收。 2,用户端:使存储了过期session_id的用户端Cookie记录过期。通过将Cookie的Expire设置为负值,要求客户端删除Cookie。 服务端:删除过期的session文件       PHP GC进程被启动以后,则会扫描session.save_path,找出过期的session,并删除该session文件。所谓,过期的 session,是指操作系统当前时间与session文件最后访问时间之差大于session.gc_maxlifetime的话,该session认 为是过期了。注意:有时候,你会发现,即便是文件过期了,有可能也没有被及时地删除掉。这是因为,每次session初始化的时候,并不会都启动PHP GC进程的,启动GC进程会大大降低php的运行效率。所有一个启动概率,这个概率由php.ini设定session.gc_probability / session.gc_divisor二个设置决定,默认概率是1%(1/1000)。这意味着,每1000次用户请求中,会启动1次PHP GC回收session文件。比如,我们下面看到的,过期的session文件依然存在:

<span># date;find /var/lib/php/session -type f -atime -1440 -print |xargs ls -lt  </span>
<span>-rw------- 1 apache apache  0 Apr 12 20:01 /var/lib/php/session/sess_5tlaq5a8im3ob1bikn62motpv7  </span>
<span>-rw------- 1 apache apache  0 Apr 12 19:39 /var/lib/php/session/sess_f7q6jfdug4ekfsjhop6jftgna7  </span>
<span>-rw------- 1 apache apache  0 Apr 12 17:03 /var/lib/php/session/sess_dk7655dqrm5m884c9nitfi7j00</span>

 

我们可以通过编辑设置,来验证启动php session的GC机制

 

 

//@file session_gc.php

ini_set("session.gc_probability", 100);

ini_set("session.gc_divisor", 100);

ini_set("session.gc_maxlifetime", 1440);

session_start();

<span>[root@localhost ~]# strace -p `cat /var/run/httpd.pid` </span>
<span>open("/var/www/html/session_gc.php", O_RDONLY) = 17
fstat64(17, {st_mode=S_IFREG|0644, st_size=144, ...}) = 0
lseek(17, 0, SEEK_CUR)                  = 0
brk(0x8d35000)                          = 0x8d35000
read(17, "<?php \nini_set(\"session.gc_probab"..., 8192) = 144
read(17, "", 8192)                      = 0
read(17, "", 8192)                      = 0
close(17)                               = 0
open("/var/lib/php/session/sess_5tlaq5a8im3ob1bikn62motpv7", O_RDWR|O_CREAT, 0600) = 17
flock(17, LOCK_EX)                      = 0
fcntl64(17, F_SETFD, FD_CLOEXEC)        = 0
fstat64(17, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
open("/var/lib/php/session", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 18
fcntl64(18, F_SETFD, FD_CLOEXEC)        = 0
time(NULL)                              = 1271125492
getdents(18, /* 13 entries */, 32768)   = 516
stat64("/var/lib/php/session/sess_bk7655dqrm5m884c9nitfi7j00", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_bk7655dqrm5m884c9nitfi7j00") = 0
stat64("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", {st_mode=S_IFREG|0600, st_size=11, ...}) = 0
unlink("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5") = 0
stat64("/var/lib/php/session/sess_n660qmcl38solbmp7vkhafqg17", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_n660qmcl38solbmp7vkhafqg17") = 0
stat64("/var/lib/php/session/sess_5tlaq5a8im3ob1bikn62motpv7", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_5tlaq5a8im3ob1bikn62motpv7") = 0
stat64("/var/lib/php/session/sess_qoa6knu9fg77un8le99o1vk1c7", {st_mode=S_IFREG|0600, st_size=31, ...}) = 0
unlink("/var/lib/php/session/sess_qoa6knu9fg77un8le99o1vk1c7") = 0
stat64("/var/lib/php/session/sess_dutbc682k3h4cgho2sgugc0id4", {st_mode=S_IFREG|0600, st_size=23, ...}) = 0
unlink("/var/lib/php/session/sess_dutbc682k3h4cgho2sgugc0id4") = 0
stat64("/var/lib/php/session/sess_vp8lfqnskjvsiilcp1c4l484d3", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_vp8lfqnskjvsiilcp1c4l484d3") = 0
stat64("/var/lib/php/session/sess_dk7655dqrm5m884c9nitfi7j00", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_dk7655dqrm5m884c9nitfi7j00") = 0
stat64("/var/lib/php/session/sess_f7q6jfdug4ekfsjhop6jftgna7", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
unlink("/var/lib/php/session/sess_f7q6jfdug4ekfsjhop6jftgna7") = 0
getdents(18, /* 0 entries */, 32768)    = 0
close(18)                               = 0
chdir("/var/lib/php/session")           = 0
pwrite64(17, "", 0, 0)                  = 0
close(17)                               = 0
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
writev(16, [{"HTTP/1.1 200 OK\r\nDate: Tue, 13 A"..., 327}], 1) = 327
write(12, "192.168.0.98 - - [13/Apr/2010:10"..., 205) = 205
shutdown(16, 1 /* send */)              = 0</span></span>

 

 

从 上面蓝色部分可以看出,通过用stat64检查session文件的状态,如果发现过期了,则会通过调用系统内核函数ulink()删除过期的 session文件。可见,session初始化的时候会启动GC, GC会扫描session.save_path中的所有session文件,查看他们状态并且将过期的文件删除。正因为如此,所以默认设置启动的概率是 1/1000。  

客户端:删除过期session id的cookie记录 如 果用户发现session已经过期,但是服务端的GC还没有启动,服务端可以手通过手工代码setcookie的方式要求用户端浏览器删除键值为 session_name()的Cookie记录。这样,下回访问的时候,浏览器以为用户是第一次访问,并且重新给访问用户分配一个新的 session_id。较好的做法类似这样:

 
<span>//@file session_destroy.php
session_start();
$sess_name = session_name();
$sess_id = session_id();
list(, $path, $domain, ,) = session_get_cookie_params();
if ($sess_name && isset($_COOKIE[$sess_name])) {
  setcookie($sess_name, '', -1, $path, $domain);
  if ($sess_id) {
    session_destroy();
  }
}</span>

 

抓取的http数据包如下:

 #
T 192.168.0.98:2638 -GET /session_destroy.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U;
Windows NT 5.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Accept: application/xml,appl
ication/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..Accept-Langu
age: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cookie: PHPSESSID=rjdtgvmueggplgqno66qlfket1....
##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Tue, 13 Apr 2010 07:08:24 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Expires: T
hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
cache..Set-Cookie: PHPSESSID=deleted; expires=Mon, 13-Apr-2009 07:08:23 GMT..Content-Length: 222..Connection: close..Con
tent-Type: text/html; charset=UTF-8....
##
T 192.168.0.98:2642 -GET /p1.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.
1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Accept: application/xml,application/xhtml
+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..Accept-Language: zh-CN,zh
;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3....
##
T 192.168.0.8:8080 -HTTP/1.1 200 OK..Date: Tue, 13 Apr 2010 07:09:15 GMT..Server: Apache/2.2.3 (CentOS)..X-Powered-By: PHP/5.1.6..Set-Cookie
: PHPSESSID=lbmk3sc5a88e9cjuekr0aa9pc3; path=/..Expires: Thu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cach
e, must-revalidate, post-check=0, pre-check=0..Pragma: no-cache..Content-Length: 11..Connection: close..Content-Type: te
xt/html; charset=UTF-8....hello world

         上面观察可以知道,通过访问/session_destroy.php,它要求客户端将session_id的Cookie记录删除。而接下来访问 /p1.php的时候,http请求头没有通过Cookie将用户的session id带给服务器(因为刚被要求删除)。而第二次请求/p1.php的http响应里头可以看到,服务端又给用户重新分配了一个新的session id,而且不会继续使用过去的session数据。  

 

与session回收相关的php.ini设置:

1, session.gc_probability和session.gc_divisor 由 这二个函数决定了启用GC的概率,默认是1/1000。也就是说,每一千次用户请求中有一次会启动GC回收session。启动GC进程不宜过于频繁。上 面的例子,我们可以看到,它会每次检查session.save_path目录下每个文件的状态。这样会降低php的执行效率。

2, session.gc_maxlifetime = 1440 设置session存活时间,单位是秒。每次GC启动后, 会通过stat得到session文件最后访问的unix时间,通过现在时间减去文件最后访问时间之间大于session.gc_maxlifetime,则会删除该文件。

总结  

1, PHP使用Cookie的方法传递session id。尽量不要使用GET方法传递session id,因为这样很不安全。

2, 可以通过setcookie()的方法,将客户端的session id的Cookie记录删除。

3, PHP GC进程由session初始化启动。但不是每一次用户请求都会被启动,它的启动概率默认是1/1000。过于频繁访问的网站,并发量大的网站,可减小PHP GC的启动频率。PHP GC回收session会降低php的执行效率。

4, 通过下面代码,优化session回收

 

session_start();

if (isset($_SESSION['SESS_TIMEOUT'])) {

  if ($_SERVER['REQUEST_TIME'] > $_SESSION['SESS_TIMEOUT']) {

    setcookie(session_name(), session_id(), -1, '/');

    session_unset();

    session_destroy();

  }

} else {

  $_SESSION['SESS_TIMEOUT'] = $_SERVER['REQUEST_TIME'] + 3600;

}

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn