php会话控制

关于php的会话控制,我有惨痛的回忆┭┮﹏┭┮,而且还是两次。第一次是我在写博客的时候,当时花了很长时间再写,当我写完并检查后,点击了保存,我勒个去,直接跳转到登录页面,点返回也返回不了,辛苦全白瞎了。另外一次是面试的时候,面试官问我如何去保证session在1个小时后一定过期。当时我的答案是设置gc_maxlifetime设置为3600。当时面试的人说,回答的不对,这样不能保证1小时后肯定失效。当然他也没和我说原因。回到家后,我觉得必须要把这个问题搞清楚。去搜索下,搜索了鸟哥的博客里有一篇刚好讲这个的。我估计面试官之前也是看过这篇文章的。文章地址贴出来:https://www.laruence.com/2012/01/10/2469.html

cookie与session

cookie是存放在客户端的(浏览器中),它可以保存一些信息在客户端,而session是存放在服务器中。他们之间是如何配合工作的呢?

  • 浏览器首次向服务端发送请求
  • 服务端开启session,即session_start(),生成唯一ID(session_id),服务器端会在指定目录生成一个以该唯一ID命名的文件(用于保存该用户的会话信息)
  • 服务端通过响应头将session_id发送给浏览器
  • 浏览器拿到session_id后,将它保存在cookie中,一般名称为PHPSESSID。

当浏览器下一次再次访问的时候,浏览器会在请求头里带上session_id的值。因为请求头里有session_id,所以服务端就不会再去生成session_id,而是会去找有没有以session_id命名的文件,去获取该文件里的内容。

通过上面所有的机制,完成了会话控制。

SESSION的垃圾回收机制

通常的php默认session的有效时间是24分钟。如果,在session文件没有被删除前,客户端来了请求,就会更新指定session文件最后的修改事假。但如果一直没有请求来,session文件最后一次修改时间离当前时间超过24分钟,就会有可能触发session回收机制。

为什么说有可能呢?因为session的垃圾回收是有概率的,它的概率由session.gc_probability和session.gc_diviso确定。概率为session.gc_probability/session.gc_diviso

php默认gc_probability是1,而gc_diviso默认为100,也就是说每次请求触发垃圾回收的概率为1/100,这个请求是指任意浏览器或其他客户端的请求。当我们网站的pv值非常大的时候,可以考虑将概率提高,比如1/1000,减轻服务端的压力。

COOKIE的过期时间

当浏览器保存服务端请求头里的session_id值及过期时间后,会将它保存在cookie里,并设置cookie的过期时间。我们知道,当浏览器发送任意请求给服务端时,服务端会刷新session文件最后过期时间。但是,cookie的过期时间会不会变呢?

答案是不会,如想刷新cookie的过期时间,需要另写相应程序来完成。

问题的原因及解决

介绍完上面的知识后,我们来解决文章开头所说两个问题。首先是如果保证session一个小时后肯定过期。因为session的垃圾回收是概率事件,所以它并不靠谱。

那么转换下思路,通过设置cookie的有效期可行吗?答案是不行。cookie0在客户端里删除了,会导致下次请求的时候不会带上这个session_id值,服务端会生成新的session_id,但旧的session文件还是存在的。

其实这个问题最简单最高效的做法,也是推荐的方法,是将session保存在redis中而不是文本文件中,通过redis自身键过期机制来保证1个小时后,自动删除该键,达到想要的功能。

上面所说是通过redis来完成的,如果只靠php可以吗?

可以通过给每个session设置一个过期时间戳,来保证一定过期。下面贴上代码

<?php
session_start([
   'cookie_lifetime' => 3600,
   'gc_maxlifetime' => 3600
]);

if (isset($_SESSION['expires_in']) && $_SESSION['expires_in'] > time()) {
   // 未过期,更新session的失效时间
   $_SESSION['expires_in'] += 3600;
} else {
   // 过期删除
   $_SESSION = [];
   session_destroy();
}

在看第二个问题,在写文章的时候如何保证会话不过期。该问题可以从两个方面去解决

  • 浏览器定时向服务端发送请求,服务端设置cookie的过期时间及session过期时间,通过响应头来刷新浏览器保存的cookie的有效期。

当我们已经有了会话时,然后弄一个js定时请求,去刷新session和cookie的过期时间。

function refresh () {
   setTimeout(function ()
  {
       $.get('./refresh.php?'+Math.random());
       refresh()
  }, 1000);
}

refresh();

服务端refresh代码如下:

session_start([
   'cookie_lifetime' => 60,
   'gc_maxlifetime' => 60
]);

if (isset($_SESSION['expires_in']) && $_SESSION['expires_in'] > time()) {
   $_SESSION['expires_in'] += 60;
   $sessionId = $_COOKIE[session_name()];
   setcookie(session_name(), $sessionId, time() + 3600, '/');
} else {
   $_SESSION = [];

   if (isset($_COOKIE[session_name()])) {
       setcookie(session_name(), '', time() - 100, '/');
  }

   session_destroy();
}