Process
21819 attached - interrupt to quit ...
st_mode=S_IFREG|0644, st_size=72, ...}) = 0
open("/var/www/html/test_session.php", O_RDONLY) = 17
fstat64(17, {st_mode=S_IFREG|0644, st_size=72, ...}) = 0
lseek(17, 0, SEEK_CUR) = 0
read(17, "
蓝色加粗,通过系统内核函数open调用打开session文件,这是由session_start()产生的调用, 注意这里并没有产生读文件操作。红色部分,将一个空字符串写入session文件。 由此可见session初始化在页面生命周期开始之时,手工调用session_start可以初始化session文件, 而在页面生命周期结束之时,会自动地注销session,结束当前session生命周期, 同时在此周期产生的session数据写回session文件,我们把这种方式结束的session,称为session默认结束。
任务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);
立即学习 “PHP免费学习笔记(深入) ”;
PHP经典实例(第二版)
PHP经典实例(第2版)能够为您节省宝贵的Web开发时间。有了这些针对真实问题的解决方案放在手边,大多数编程难题都会迎刃而解。《PHP经典实例(第2版)》将PHP的特性与经典实例丛书的独特形式组合到一起,足以帮您成功地构建跨浏览器的Web应用程序。在这个修订版中,您可以更加方便地找到各种编程问题的解决方案,《PHP经典实例(第2版)》中内容涵盖了:表单处理;Session管理;数据库交互;使用We
下载
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);
立即学习 “PHP免费学习笔记(深入) ”;
//--
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);
立即学习 “PHP免费学习笔记(深入) ”;
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);
立即学习 “PHP免费学习笔记(深入) ”;
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);
立即学习 “PHP免费学习笔记(深入) ”;
echo " ---5--- ";
session_unregister('pg_boss'); //pg_boss不会被清除,还为NULL
session_unset(); //不会清空$_SESSION数组,因为session已被session_destroy注销
var_dump($_SESSION);
立即学习 “PHP免费学习笔记(深入) ”;
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文件
[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的变化
session_start();
$_SESSION['pfid'] = 123;
var_dump($_SESSION);
session_regenerate_id();
var_dump($_SESSION);
fopen(__FILE__, "r"); [root@localhost ~]# strace -p `cat /var/run/httpd.pid`
Process
22641 attached - interrupt to quit @@######@@
观察测试结果,蓝色部分照旧是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文件还是依然存在的
...
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, "\n int(12"..., 75}], 2) = 461
write(12, "192.168.0.98 - - [11/Apr/2010:00"..., 210) = 210
shutdown(16, 1 /* send */) = 0
[root@localhost html]# ls -lt /var/lib/php/session
-rw------- 1 apache apache 31 Apr 11 03:18 sess_qoa6knu9fg77un8le99o1vk1c7
总结:
1, 用户注销web应用系统,最好的调用方式依次是 session_unset(); session_destroy(); unset($_SESSION);
-rw------- 1 apache apache 11 Apr 11 00:08 sess_4j38nv7l1fq1bj6n80l6g9cum5 … function user_sigout() {
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发送给服务,观察服务端响应。我们写一个测试脚本,如下:
global $user;
sys_event_register('user_sigout', $user);
session_unset(); //清空session
session_destroy(); //删除session文件
if (isset($_SESSION)) {
unset($_SESSIONI); //注销$_SESSION
}
return TRUE;
}
抓到的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文件依然存在:
$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;
}
# 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
我们可以通过编辑设置,来验证启动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();
@@######@@
@@######@@
从上面蓝色部分可以看出,通过用stat64检查session文件的状态,如果发现过期了,则会通过调用系统内核函数ulink()删除过期的session文件。可见,session初始化的时候会启动GC, GC会扫描session.save_path中的所有session文件,查看他们状态并且将过期的文件删除。正因为如此,所以默认设置启动的概率是1/1000。
客户端:删除过期session id的cookie记录
如果用户发现session已经过期,但是服务端的GC还没有启动,服务端可以手通过手工代码setcookie的方式要求用户端浏览器删除键值为session_name()的Cookie记录。这样,下回访问的时候,浏览器以为用户是第一次访问,并且重新给访问用户分配一个新的session_id。较好的做法类似这样:
-rw------- 1 apache apache 0 Apr 12 17:03 /var/lib/php/session/sess_dk7655dqrm5m884c9nitfi7j00
抓取的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;
}
[root@localhost ~]# strace -p `cat /var/run/httpd.pid` 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, " //@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();
}
}
PHP速学教程(入门到精通)
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
下载
本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
任务管理器教程
本专题整合了任务管理器相关教程,阅读下面的文章了解更多详细操作。
AppleID格式
本专题整合了AppleID相关内容,阅读专题下面的文章了解更多详细教程。
添加脚注通用方法
本专题整合了添加脚注方法合集,阅读专题下面的文章了解更多内容。
重启电脑教程汇总
本专题整合了重启电脑操作教程,阅读下面的文章了解更多详细教程。
纸张尺寸汇总
本专题整合了纸张尺寸相关内容,阅读专题下面的文章了解更多内容。
Java Spring Boot 微服务实战
本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。
网站特效
/
网站源码
/
网站素材
/
前端模板