首页 > 编程 > PHP > 正文

PHPsession详解

2020-03-22 16:30:56
字体:
来源:转载
供稿:网友
  • 个人博客原文:http://www.phpthinking.com/archives/318


    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数据的html' target='_blank'>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操作

    1<?php
    2//@file test_session_2.php
    3session_start();
    4$pg_uuid = 'ac606826-9620-490b-b850-ea9dbce6cfd5';
    5
    6//注册全局变量pg_uuid到session,但$_SESSION['pg_uuid']值为NULL,只影响$_SESSION
    7session_register('pg_uuid');
    8var_dump($_SESSION);
    9fopen(__FILE__, "r");

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

    01Process 21819 attached - interrupt to quit
    02...
    03st_mode=S_IFREG|0644, st_size=72, ...}) = 0
    04open("/var/www/html/test_session.php", O_RDONLY) = 17
    05fstat64(17, {st_mode=S_IFREG|0644, st_size=72, ...}) = 0
    06lseek(17, 0, SEEK_CUR) = 0
    07read(17, "<p;?php/n//@file test_session.php/ns"..., 8192) = 72
    08read(17, "", 8192) = 0
    09read(17, "", 8192) = 0
    10close(17) = 0
    11gettimeofday({1270906664, 11602}, NULL) = 0
    12open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17
    13flock(17, LOCK_EX) = 0
    14fcntl64(17, F_SETFD, FD_CLOEXEC) = 0
    15fstat64(17, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
    16time(NULL) = 1270906664
    17open("/var/www/html/test_session.php", O_RDONLY) = 18
    18fstat64(18, {st_mode=S_IFREG|0644, st_size=72, ...}) = 0
    19lseek(18, 0, SEEK_CUR) = 0
    20close(18) = 0
    21chdir("/var/lib/php/session") = 0
    22pwrite64(17, "", 0, 0) = 0
    23close(17) = 0
    24setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
    25writev(16, [{"HTTP/1.1 200 OK/r/nDate: Sat, 10 A"..., 385}], 1) = 385
    26write(12, "192.168.0.98 - - [10/Apr/2010:21"..., 207) = 207
    27shutdown(16, 1 /* send */) = 0
    28epoll_wait(15,

    蓝色加粗,通过系统内核函数open调用打开session文件,这是由session_start()产生的调用,
    注意这里并没有产生读文件操作。红色部分,将一个空字符串写入session文件。
    由此可见session初始化在页面生命周期开始之时,手工调用session_start可以初始化session文件,
    而在页面生命周期结束之时,会自动地注销session,结束当前session生命周期,
    同时在此周期产生的session数据写回session文件,我们把这种方式结束的session,称为session默认结束。

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

    1<?php
    2//@file test_session_2.php
    3session_start();
    4$pg_uuid = 'ac606826-9620-490b-b850-ea9dbce6cfd5';
    5session_register('pg_uuid'); //注册全局变量pg_uuid到session,但值为NULL,只影响$_SESSION
    6var_dump($_SESSION);
    7fopen(__FILE__, "r");

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

    01Process 21819 attached - interrupt to quit
    02...
    03open("/var/www/html/test_session_2.php", O_RDONLY) = 17
    04fstat64(17, {st_mode=S_IFREG|0644, st_size=148, ...}) = 0
    05lseek(17, 0, SEEK_CUR) = 0
    06read(17, "<?php/nsession_start();/n$pg_uuid "..., 8192) = 148
    07read(17, "", 8192) = 0
    08read(17, "", 8192) = 0
    09close(17) = 0
    10open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17
    11flock(17, LOCK_EX) = 0
    12fcntl64(17, F_SETFD, FD_CLOEXEC) = 0
    13fstat64(17, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
    14time(NULL) = 1270907613
    15open("/var/www/html/test_session_2.php", O_RDONLY) = 18
    16fstat64(18, {st_mode=S_IFREG|0644, st_size=148, ...}) = 0
    17lseek(18, 0, SEEK_CUR) = 0
    18close(18) = 0
    19chdir("/var/lib/php/session") = 0
    20pwrite64(17, "pg_uuid|N;", 10, 0) = 10
    21close(17) = 0
    22setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
    23writev(16, [{"HTTP/1.1 200 OK/r/nDate: Sat, 10 A"..., 328}, {"array(1) {/n [/"pg_uuid/"]=>/n NUL"..., 36}], 2) = 364
    24write(12, "192.168.0.98 - - [10/Apr/2010:21"..., 210) = 210
    25shutdown(16, 1 /* send */) = 0
    26epoll_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()的区别

    01<?php
    02session_start();
    03echo "<br/>---1--<br/>";
    04$pg_uid = 1;
    05//$_SESSION['pg_uid']; //该行会报一个Notice消息,即没有初始化该变量
    06$_SESSION['pg_name'] = 'boys'; //填入到$_SESSION变量,但不立即写入session文件,值为boys
    07$pg_sex = 1;
    08$pg_theme = 'default';
    09session_register('pg_sex'); //填入到$_SESSION变量,但不立即写入session文件,值为NULL
    10session_register('pg_theme'); //填入到$_SESSION变量,但不立即写入session文件,值为NULL
    11var_dump($_SESSION);
    12
    13//--
    14echo "<br/>---2--<br/>";
    15unset($_SESSION['pg_theme']); //从$_SESSION清除该元素,不立即同步到session文件
    16unset($_SESSION['pg_name']); //从$_SESSION清除该元素,不立即同步到session文件
    17session_unregister('pg_sex'); //从$_SESSION清除该元素,不立即同步到session文件
    18session_unregister('pg_uid'); //从$_SESSION清除该元素,不立即同步到session文件
    19var_dump($_SESSION);
    20
    21echo "<br/>---3--<br/>";
    22$_SESSION['pg_members'] = 5; //填入$_SESSION数组,但不立即同步到session文件,值为5
    23$pg_boy = 6;
    24session_register('pg_boy'); //填入$_SESSION数组,但不立即同步到session文件,值为NULL
    25session_unset($_SESSION); //清空$_SESSION
    26var_dump($_SESSION);
    27
    28echo "<br/>---4--<br/>";
    29$_SESSION['pg_boss'] = 3; //填入$_SESSION数组,但不立即同步到session文件,值为3
    30$pg_girls = 6;
    31session_register('pg_girls'); //填入$_session数组,但不立即同步到session文件,值为NULL
    32session_destroy(); //注销session_destroy
    33var_dump($_SESSION);
    34
    35echo "<br/>---5---<br/>";
    36session_unregister('pg_boss'); //pg_boss不会被清除,还为NULL
    37session_unset(); //不会清空$_SESSION数组,因为session已被session_destroy注销
    38var_dump($_SESSION);
    39
    40fopen(__FILE__, "r");
    41
    42//@这里是页面析构的时候-- 本应该将$_SESSION数据同步到session文件, 真的吗???
    43//@事实,没有发生任何IO操作,即没有将$_SESSION数据回写,怎么回事???
    44//@因为被session_destroy()消毁了session...
    45[php]
    46程序输出:
    47[php]
    48---1--
    49array(3) { ["pg_name"]=> string(4) "boys" ["pg_sex"]=> NULL ["pg_theme"]=> NULL }
    50---2--
    51array(0) { }
    52---3--
    53array(0) { }
    54---4--
    55array(2) { ["pg_boss"]=> int(3) ["pg_girls"]=> NULL }
    56---5---
    57array(2) { ["pg_boss"]=> int(3) ["pg_girls"]=> NULL }

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

    01Process 21819 attached - interrupt to quit
    02...
    03open("/var/www/html/test_session_3.php", O_RDONLY) = 17
    04fstat64(17, {st_mode=S_IFREG|0644, st_size=706, ...}) = 0
    05lseek(17, 0, SEEK_CUR) = 0
    06read(17, "<?php/nsession_start();/necho /"<br"..., 8192) = 706
    07read(17, "", 8192) = 0
    08read(17, "", 8192) = 0
    09close(17) = 0
    10open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17
    11flock(17, LOCK_EX) = 0
    12fcntl64(17, F_SETFD, FD_CLOEXEC) = 0
    13fstat64(17, {st_mode=S_IFREG|0600, st_size=10, ...}) = 0
    14pread64(17, "pg_uuid|N;", 10, 0) = 10
    15close(17) = 0
    16unlink("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5") = 0
    17time(NULL) = 1270910665
    18open("/var/www/html/test_session_3.php", O_RDONLY) = 17
    19fstat64(17, {st_mode=S_IFREG|0644, st_size=706, ...}) = 0
    20lseek(17, 0, SEEK_CUR) = 0
    21close(17) = 0
    22chdir("/var/lib/php/session") = 0
    23setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
    24...
    25write(12, "192.168.0.98 - - [10/Apr/2010:22"..., 211) = 211
    26shutdown(16, 1 /* send */) = 0

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

    1[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的变化

    1<?php
    2session_start();
    3$_SESSION['pfid'] = 123;
    4var_dump($_SESSION);
    5session_regenerate_id();
    6var_dump($_SESSION);
    7fopen(__FILE__, "r");

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

    01Process 22641 attached - interrupt to quit
    02...
    03open("/var/www/html/test_session_4.php", O_RDONLY) = 17
    04fstat64(17, {st_mode=S_IFREG|0644, st_size=141, ...}) = 0
    05lseek(17, 0, SEEK_CUR) = 0
    06read(17, "<?php/nsession_start();/n$_SESSION"..., 8192) = 141
    07read(17, "", 8192) = 0
    08read(17, "", 8192) = 0
    09close(17) = 0
    10open("/var/lib/php/session/sess_4j38nv7l1fq1bj6n80l6g9cum5", O_RDWR|O_CREAT, 0600) = 17
    11flock(17, LOCK_EX) = 0
    12fcntl64(17, F_SETFD, FD_CLOEXEC) = 0
    13fstat64(17, {st_mode=S_IFREG|0600, st_size=11, ...}) = 0
    14pread64(17, "pfid|i:123;", 11, 0) = 11
    15gettimeofday({1270915896, 122016}, NULL) = 0
    16time(NULL) = 1270915896
    17open("/var/www/html/test_session_4.php", O_RDONLY) = 18
    18fstat64(18, {st_mode=S_IFREG|0644, st_size=141, ...}) = 0
    19lseek(18, 0, SEEK_CUR) = 0
    20close(18) = 0
    21chdir("/var/lib/php/session") = 0
    22close(17) = 0
    23open("/var/lib/php/session/sess_qoa6knu9fg77un8le99o1vk1c7", O_RDWR|O_CREAT, 0600) = 17
    24flock(17, LOCK_EX) = 0
    25fcntl64(17, F_SETFD, FD_CLOEXEC) = 0
    26pwrite64(17, "pfid|i:123;", 11, 0) = 11
    27close(17) = 0
    28setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
    29writev(16, [{"HTTP/1.1 200 OK/r/nDate: Sat, 10 A"..., 386}, {"array(1) {/n [/"pfid/"]=>/n int(12"..., 75}], 2) = 461
    30write(12, "192.168.0.98 - - [11/Apr/2010:00"..., 210) = 210
    31shutdown(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);

    01<?php
    02function user_sigout() {
    03global $user;
    04sys_event_register('user_sigout', $user);
    05
    06session_unset(); //清空session
    07session_destroy(); //删除session文件
    08if (isset($_SESSION)) {
    09unset($_SESSIONI); //注销$_SESSION
    10}
    11return TRUE;
    12}

    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记录过期,则会被浏览器删除。之后的下一次请求,服务器会以为用户又是第一次访问,如此循环。
    让我们通过测与来验证以上的陈述

    01<?php
    02//p1.php
    03session_start();
    04通过截取到的http数据包如下
    05#
    06T 192.168.0.98:2290 -< 192.168.0.8:8080 [AP]
    07GET /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
    08; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Accept: application/xml,application/xhtml+
    09xml,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;
    10q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3….
    11##
    12T 192.168.0.8:8080 -< 192.168.0.98:2290 [AP]
    13HTTP/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
    14: PHPSESSID=bk7655dqrm5m884c9nitfi7j00; path=/..Expires: Thu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cach
    15e, must-revalidate, post-check=0, pre-check=0..Pragma: no-cache..Content-Length: 0..Connection: close..Content-Type: tex
    16t/html; charset=UTF-8….

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

    01#
    02T 192.168.0.98:2314 -< 192.168.0.8:8080 [AP]
    03GET /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
    04; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Cache-Control: max-age=0..Accept: applicat
    05ion/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..
    06Accept-Language: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cookie: PHPSESSID=bk7655dqrm5m884c9nitfi7j00..
    07..
    08##
    09T 192.168.0.8:8080 -< 192.168.0.98:2314 [AP]
    10HTTP/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
    11hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
    12cache..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发送给服务,观察服务端响应。我们写一个测试脚本,如下:

    01<?php
    02$host = '192.168.0.8';
    03$port = 8080;
    04$path = '/p1.php';
    05$sid = "PHPSESSID=dk7655dqrm5m884c9nitfi7j00";
    06
    07$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
    08if ($fp) {
    09fputs($fp, "GET {$path} HTTTP/1.1/r/n");
    10fputs($fp, "Host: {$host}/r/n");
    11fputs($fp, "Cookie: {$sid}/r/n");
    12fputs($fp, "Connection: close/r/n/r/n");
    13while (!feof($fp)) {
    14$d .= fgets($fp, 4096);
    15}
    16fclose();
    17echo $d;
    18}

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

    1#
    2T 192.168.0.98:2400 -< 192.168.0.8:8080 [AP]
    3GET /p1.php HTTTP/1.1..
    4Host: 192.168.0.8..Cookie: PHPSESSID=dk7655dqrm5m884c9nitfi7j00..Connection: close….
    5##
    6T 192.168.0.8:8080 -< 192.168.0.98:2400 [AP]
    7HTTP/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
    8hu, 19 Nov 1981 08:52:00 GMT..Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0..Pragma: no-
    9cache..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的情况

    1<?php
    2//@file: p2.php
    3session_start();
    4session_regenerate_id();

    抓取到的http数据包如下

    01####
    02T 192.168.0.98:2763 -< 192.168.0.8:8080 [AP]
    03GET /p2.php HTTP/1.1..Host: 192.168.0.8:8080..Connection: keep-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.
    041; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Cache-Control: max-age=0..Accept: applica
    05tion/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch.
    06.Accept-Language: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cookie: PHPSESSID=bk7655dqrm5m884c9nitfi7j00.
    07… ##
    08T 192.168.0.8:8080 -< 192.168.0.98:2763 [AP]
    09HTTP/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
    10PHP编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

    发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表