扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
Session 和 Cookie 有什么关系
十余年的潞城网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整潞城建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联公司从事“潞城网站设计”,“潞城网站推广”以来,每个客户项目都认真落实执行。
Cookie 也是由于 HTTP 无状态的特点而产生的技术。也被用于保存访问者的身份标示和一些数据。每次客户端发起 HTTP 请求时,会将 Cookie 数据加到 HTTP header 中,提交给服务端。这样服务端就可以根据 Cookie 的内容知道访问者的信息了。 可以说,Session 和 Cookie 做着相似的事情,只是 Session 是将数据保存在服务端,通过客户端提交来的 session_id 来获取对应的数据;而 Cookie 是将数据保存在客户端,每次发起请求时将数据提交给服务端的。
上面提到,session_id 可以通过 URL 或 cookie 来传递,由于 URL 的方式比 cookie 的方式更加不安全且使用不方便,所以一般是采用 cookie 来传递 session_id。
服务端生成 session_id,通过 HTTP 报文发送给客户端(比如浏览器),客户端收到后按指示创建保存着 session_id 的 cookie。cookie 是以 key/value 形式保存的,看上去大概就这个样子的:PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2。在 PHP 中,保存 session_id 的 cookie 名称默认叫作 PHPSESSID,这个名称可以通过 php.ini 中 session.name 来修改,也可以通过函数 session_name() 来修改。
为什么不推荐使用 PHP 自带的 files 型 Session 处理器
在 PHP 中,默认的 Session 处理器是 files,处理器可以用户自己实现(参见:自定义会话管理器)。我知道的成熟的 Session 处理器还有很多:Redis、Memcached、MongoDB……
为什么不推荐使用 PHP 自带的 files 类型处理器,PHP 官方手册中给出过这样一段 Note:
无论是通过调用函数 session_start() 手动开启会话, 还是使用配置项 session.auto_start 自动开启会话, 对于基于文件的会话数据保存(PHP 的默认行为)而言, 在会话开始的时候都会给会话数据文件加锁, 直到 PHP 脚本执行完毕或者显式调用 session_write_close() 来保存会话数据。 在此期间,其他脚本不可以访问同一个会话数据文件。
上述引用参见:Session 的基本用法
为了证明这段话,我们创建一下 2 个文件: 文件:session1.php
?php
session_start();
sleep(5);
var_dump($_SESSION);
?
文件:session2.php
?php
session_start();
var_dump($_SESSION);
?
在同一个浏览器中,先访问 ,然后在当前浏览器新的标签页立刻访问 。实验发现,session1.php 等了 5 秒钟才有输出,而 session2.php 也等到了将近 5 秒才有输出。而单独访问 session2.php 是秒开的。在一个浏览器中访问 session1.php,然后立刻在另外一个浏览器中访问 session2.php。结果是 session1.php 等待 5 秒钟有输出,而 session2.php 是秒开的。
分析一下造成这个现象的原因:上面例子中,默认使用 Cookie 来传递 session_id,而且 Cookie 的作用域是相同。这样,在同一个浏览器中访问这 2 个地址,提交给服务器的 session_id 就是相同的(这样才能标记访问者,这是我们期望的效果)。当访问 session1.php 时,PHP 根据提交的 session_id,在服务器保存 Session 文件的路径(默认为 /tmp,通过 php.ini 中的 session.save_path 或者函数 session_save_path() 来修改)中找到了对应的 Session 文件,并对其加锁。如果不显式调用 session_write_close(),那么直到当前 PHP 脚本执行完毕才会释放文件锁。如果在脚本中有比较耗时的操作(比如例子中的 sleep(5)),那么另一个持有相同 session_id 的请求由于文件被锁,所以只能被迫等待,于是就发生了请求阻塞的情况。
既然如此,在使用完 Session 后,立刻显示调用 session_write_close() 是不是就解决问题了哩?比如上面例子中,在 sleep(5) 前面调用 session_write_close()。
确实,这样 session2.php 就不会被 session1.php 所阻塞。但是,显示调用了 session_write_close() 就意味着将数据写到文件中并结束当前会话。那么,在后面代码中要使用 Session 时,必须重新调用 session_start()。
例如:
?php
session_start();
$_SESSION['name'] = 'Jing';
var_dump($_SESSION);
session_write_close();
sleep(5);
session_start();
$_SESSION['name'] = 'Mr.Jing';
var_dump($_SESSION);
?
官方给出的方案:
对于大量使用 Ajax 或者并发请求的网站而言,这可能是一个严重的问题。 解决这个问题最简单的做法是如果修改了会话中的变量, 那么应该尽快调用 session_write_close() 来保存会话数据并释放文件锁。 还有一种选择就是使用支持并发操作的会话保存管理器来替代文件会话保存管理器。
我推荐的方式是使用 Redis 作为 Session 的处理器。
拓展阅读:
为什么不能用 memcached 存储 Session
如何使用 Redis 作为 PHP Session handler
Session 数据是什么时候被删除的
这是一道经常被面试官问起的问题。
先看看官方手册中的说明:
session.gc_maxlifetime 指定过了多少秒之后数据就会被视为"垃圾"并被清除。 垃圾搜集可能会在 session 启动的时候开始( 取决于 session.gc_probability 和 session.gc_divisor)。 session.gc_probability 与 session.gc_divisor 合起来用来管理 gc(garbage collection 垃圾回收)进程启动的概率。此概率用 gc_probability/gc_divisor 计算得来。例如 1/100 意味着在每个请求中有 1% 的概率启动 gc 进程。session.gc_probability 默认为 1,session.gc_divisor 默认为 100。
继续用我上面那个不太恰当的比方吧:如果我们把物品放在超市的储物箱中而不取走,过了很久(比如一个月),那么保安就要清理这些储物箱中的物品了。当然并不是超过期限了保安就一定会来清理,也许他懒,又或者他压根就没有想起来这件事情。
再看看两段手册的引用:
如果使用默认的基于文件的会话处理器,则文件系统必须保持跟踪访问时间(atime)。Windows FAT 文件系统不行,因此如果必须使用 FAT 文件系统或者其他不能跟踪 atime 的文件系统,那就不得不想别的办法来处理会话数据的垃圾回收。自 PHP 4.2.3 起用 mtime(修改时间)来代替了 atime。因此对于不能跟踪 atime 的文件系统也没问题了。
GC 的运行时机并不是精准的,带有一定的或然性,所以这个设置项并不能确保旧的会话数据被删除。某些会话存储处理模块不使用此设置项。
对于这种删除机制,我是存疑的。
比如 gc_probability/gc_divisor 设置得比较大,或者网站的请求量比较大,那么 GC 进程启动就会比较频繁。
还有,GC 进程启动后都需要遍历 Session 文件列表,对比文件的修改时间和服务端的当前时间,判断文件是否过期而决定是否删除文件。
这也是我觉得不应该使用 PHP 自带的 files 型 Session 处理器的原因。而 Redis 或 Memcached 天生就支持 key/value 过期机制的,用于作为会话处理器很合适。或者自己实现一个基于文件的处理器,当根据 session_id 获取对应的单个 Session 文件时判断文件是否过期。
为什么重启浏览器后 Session 数据就取不到了
session.cookie_lifetime 以秒数指定了发送到浏览器的 cookie 的生命周期。值为 0 表示"直到关闭浏览器"。默认为 0。
其实,并不是 Session 数据被删除(也有可能是,概率比较小,参见上一节)。只是关闭浏览器时,保存 session_id 的 Cookie 没有了。也就是你弄丢了打开超市储物箱的钥匙(session_id)。
同理,浏览器 Cookie 被手动清除或者其他软件清除也会造成这个结果。
为什么浏览器开着,我很久没有操作就被登出了
这个是称为“防呆”,为了保护用户账户安全的。
这个小节放进来,是因为这个功能的实现可能和 Session 的删除机制有关(之所以说是可能,是因为这个功能不一定要借住 Session 实现,用 Cookie 也同样可以实现)。 说简单一点,就是长时间没有操作,服务端的 Session 文件过期被删除了。
一个有意思的事情
在我试验的过程中,发现了小有意思的事情:我把 GC 启动的概率设置为 100%。如果只有一个访问者请求,该访问者即使过了很久(超过了过期时间)后才发起第二次请求,那么 Session 数据也还是存在的('session.save_path' 目录下面的 Session 文件存在)。是的,明明就超过了过期时间,却没有被 GC 删除。这时,我用另外一个浏览器访问时(相对于另一个访问者),这次请求生成了新的 Session 文件,而上一个浏览器请求生成的那个 Session 文件终于没有了(之前那个 Session 文件在 'session.save_path' 目录下面的消失了)。
还有,发现 Session 文件被删除后,再次请求,还是会生成和之前文件名相同的 Session 文件(因为浏览器并没有关闭,再次请求发送的 session_id 是相同的,所以重新生成的 Session 文件的文件名还是一样的)。但是,我不理解的是:这个重新出现的文件的创建时间竟然是第一次的那个创建时间,难道它是从回收站中回来的?(确实,我做这个试验时是在 window 下进行的)
我猜测的原因是这样:当启动会话后,PHP 根据 session_id 找到并打开了对应的 Session 文件,然后才启动 GC 进程。GC 进程就只检查除了当前这个 Session 文件外的其他文件,发现过期的就干掉。所有,即使当前这个 Session 文件已经过期了,GC 也没有删除它。
我认为这个不合理的。
由于发生这种情况影响也不大(毕竟线上请求很多,当前请求的过期文件被其他请求唤起的 GC 干掉的可能性是比较大的) + 我没有信心去看 PHP 源代码 + 我并不在线上使用 PHP 自带的 files 型 Session 处理器。所以,这个问题我就没有深入研究了。请谅解。
?php
// 过期时间设置为 30 秒
ini_set('session.gc_maxlifetime', '30');
// GC 启动概率设置为 100%
ini_set('session.gc_probability', '100');
ini_set('session.gc_divisor', '100');
session_start();
$_SESSION['name'] = 'Jing';
var_dump($_SESSION);
?
当然是在服务器端,但不是保存在内存中,而是保存在文件或数据库中。
默认情况下,php.ini
中设置的
SESSION
保存方式是
files(session.save_handler
=
files),即使用读写文件的方式保存
SESSION
数据,而
SESSION
文件保存的目录由
session.save_path
指定,文件名以
sess_
为前缀,后跟
SESSION
ID,如:sess_c72665af28a8b14c0fe11afe3b59b51b。文件中的数据即是序列化之后的
SESSION
数据了。
如果访问量大,可能产生的
SESSION
文件会比较多,这时可以设置分级目录进行
SESSION
文件的保存,效率会提高很多,设置方法
为:session.save_path="N;/save_path",N
为分级的级数,save_path
为开始目录。
当写入
SESSION
数据的时候,PHP
会获取到客户端的
SESSION_ID,然后根据这个
SESSION
ID
到指定的
SESSION
文件保存目录中找到相应的
SESSION
文件,不存在则创建之,最后将数据序列化之后写入文件。读取
SESSION
数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的
SESSION
变量。
客户端数据存储机制cookie
在实际的Web应用中经常需要在客户端存储一些客户信息 一方面是为了改善用户体验(如存储访问密码 历史表单信息) 另一方面有效地减轻了服务器数据读取压力 访问者在第一次访问页面时设置cookie变量是存储在客户端计算机中 当下次浏览器请求某个页面时 就可以读取cookie中存储的值 从而实现客户端数据存取
下面将通过一个例子展示一个客户端数据存储的实际应用 执行流程如下
( )创建一个存储访问者名字的cookie;
( )当访问者首次访问网站时 访问者会被要求填写姓名信息 该姓名信息会存储于cookie中
( )当访问者再次访问网站时浏览器会显示欢迎词信息
在JavaScript创建cookie
创建一个可在cookie变量中存储访问者姓名的函数 代码如下
function setCookie(c_name value expiredays)
{
var exdate=new Date() //实例化日期变量
exdate setDate(exdate getDate()+expiredays) //设置日期变量
document cookie=c_name+ = +escape(value)+
((expiredays==null) ? : ;expires= +exdate toGMTString())
}
【代码解读】
setCookie()函数的功能是设置cookie的名称 值及失效时间 首先将获取当前的日期转换为有效的日期 然后设置cookie名称 值及失效时间 并将该值存入document cookie对象
从JavaScript取回cookie的值
从JavaScript取回cookie的值 创建读取客户端cookie值的函数 同时判断当前cookie的状态 代码如下
function getCookie(c_name)
{
if (document cookie length ) //判断当前cookie是否为空
{
c_start=document cookie indexOf(c_name + = )
if (c_start!= )
{
c_startc_start=c_start + c_name length+
c_end=document cookie indexOf( ; c_start)
if (c_end== ) c_end=document cookie length
return unescape(document cookie substring(c_start c_end))
//返回客户端cookie的值
}
}
return
}
【代码解读】
getCookie()函数首先会判断document cookie对象中是否存有cookie 如果document cookie对象存有某些cookie值 那么会继续检查指定的cookie是否已储存 如果满足判断条件就返回cookie存储值 否则返回空字符串
JavaScript流程控制
当完成了cookie的创建和读取之后 需要创建一个流程控制函数 这个函数的作用是 如果cookie已设置 则显示欢迎词 否则显示提示框要求用户输入名字
function checkCookie() {
username=getCookie( username ) //获得cookie中的用户名变量
if (username != null username != ) {
alert( Wele again +username+ ! ) //弹出欢迎信息
} else {
username=prompt( Please enter your name: )
if (username != null username != ) {
setCookie( username username ) //设置cookie
}
}
}
客户端数据存储示例的完整代码如下
lishixinzhi/Article/program/PHP/201311/21526
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流