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操作
//注册全局变量pg_uuid到session,但$_session['pg_uuid']值为null,只影响$_sessionsession_register('pg_uuid');var_dump($_session);fopen(__file__, r);
[root@localhost ~]# strace -p `cat /var/run/httpd.pid`
process 21819 attached - interrupt to quit...st_mode=s_ifreg|0644, st_size=72, ...}) = 0open(/var/www/html/test_session.php, o_rdonly) = 17fstat64(17, {st_mode=s_ifreg|0644, st_size=72, ...}) = 0lseek(17, 0, seek_cur) = 0read(17,
蓝色加粗,通过系统内核函数open调用打开session文件,这是由session_start()产生的调用,
注意这里并没有产生读文件操作。红色部分,将一个空字符串写入session文件。
由此可见session初始化在页面生命周期开始之时,手工调用session_start可以初始化session文件,
而在页面生命周期结束之时,会自动地注销session,结束当前session生命周期,
同时在此周期产生的session数据写回session文件,我们把这种方式结束的session,称为session默认结束。 任务2: 观察session_register()查看它是否会产生磁盘操作,还是只操作$_session。
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文件
[root@localhost html]# ls /var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5 ls: /var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5: no such file or directory
注意: 虽然删除了session文件,但用户再次访问web的时候,并不会给用户重新分配一个新的session id,而是依然用该session id,并且会重新创建文件名相同的session文件,即sess_session-id
任务4: 测试并观察session_regenerate_id行为,以及$_session的变化
[root@localhost ~]# strace -p `cat /var/run/httpd.pid`process 22641 attached - interrupt to quit...open(/var/www/html/test_session_4.php, o_rdonly) = 17fstat64(17, {st_mode=s_ifreg|0644, st_size=141, ...}) = 0lseek(17, 0, seek_cur) = 0read(17, \n int(12..., 75}], 2) = 461write(12, 192.168.0.98 - - [11/apr/2010:00..., 210) = 210shutdown(16, 1 /* send */) = 0
观察测试结果,蓝色部分照旧是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文件还是依然存在的
[root@localhost html]# ls -lt /var/lib/php/session
-rw------- 1 apache apache 31 apr 11 03:18 sess_qoa6knu9fg77un8le99o1vk1c7
-rw------- 1 apache apache 11 apr 11 00:08 sess_4j38nv7l1fq1bj6n80l6g9cum5 …
总结:
1, 用户注销web应用系统,最好的调用方式依次是 session_unset(); session_destroy(); unset($_session);
global $user; sys_event_register('user_sigout', $user); session_unset(); //清空session session_destroy(); //删除session文件 if (isset($_session)) { unset($_sessioni); //注销$_session } return true;}
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记录过期,则会被浏览器删除。之后的下一次请求,服务器会以为用户又是第一次访问,如此循环。
让我们通过测与来验证以上的陈述
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发送给服务,观察服务端响应。我们写一个测试脚本,如下:
抓到的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的情况
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文件依然存在:
# date;find /var/lib/php/session -type f -atime -1440 -print |xargs ls -lt
-rw------- 1 apache apache 0 apr 12 20:01 /var/lib/php/session/sess_5tlaq5a8im3ob1bikn62motpv7
-rw------- 1 apache apache 0 apr 12 19:39 /var/lib/php/session/sess_f7q6jfdug4ekfsjhop6jftgna7
-rw------- 1 apache apache 0 apr 12 17:03 /var/lib/php/session/sess_dk7655dqrm5m884c9nitfi7j00
我们可以通过编辑设置,来验证启动php session的gc机制
ini_set(session.gc_probability, 100);
ini_set(session.gc_divisor, 100);
ini_set(session.gc_maxlifetime, 1440);
session_start();
[root@localhost ~]# strace -p `cat /var/run/httpd.pid`
open(/var/www/html/session_gc.php, o_rdonly) = 17fstat64(17, {st_mode=s_ifreg|0644, st_size=144, ...}) = 0lseek(17, 0, seek_cur) = 0brk(0x8d35000) = 0x8d35000read(17, 从上面蓝色部分可以看出,通过用stat64检查session文件的状态,如果发现过期了,则会通过调用系统内核函数ulink()删除过期的session文件。可见,session初始化的时候会启动gc, gc会扫描session.save_path中的所有session文件,查看他们状态并且将过期的文件删除。正因为如此,所以默认设置启动的概率是1/1000。
客户端:删除过期session id的cookie记录
如果用户发现session已经过期,但是服务端的gc还没有启动,服务端可以手通过手工代码setcookie的方式要求用户端浏览器删除键值为session_name()的cookie记录。这样,下回访问的时候,浏览器以为用户是第一次访问,并且重新给访问用户分配一个新的session_id。较好的做法类似这样:
抓取的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['sess_timeout']) {
setcookie(session_name(), session_id(), -1, '/');
session_unset();
session_destroy();
}
} else {
$_session['sess_timeout'] = $_server['request_time'] + 3600;
}
