2015年10月

title = ""

date=

tags=["PHP"]

+++

我们知道,Session是一种会话技术,用来实现跨脚本共享数据。Session是存放在服务器端的文件里的,因此Session有可能因为文件数量过多,会在查询 Session 文件以及读取的时候产生压力。一般我们有三种解决方案

  1. 使用文件分层(缺点:I/O操作是系统的一个瓶颈,即使分层也不能避免此问题)
  2. 将session放入数据库
  3. 将session放在内存中(非关系性数据库)(缺点:对服务器内存要求教高)

将Session存入MySQL数据库,也就是我们要讲的重点(其实并不建议这么做,因为在高并发和大流量的情况下,无疑会增加MySQL的压力,建议放到缓存服务中,如Memcache、Redis),

要实现session入库,首先我们要了解session 机制:

在PHP中,Session 可以理解为一套单独的小系统,在该系统中有很多关于 Session 的处理方法,用来解决各种问题,用户只需要在 Session 之外,调用session_start函数(session系统的一个接口)

其他操作都是session系统帮忙去处理

img

由图可知我们应该修改session机制中的session的读取和最终的写入。要修改session机制要借助一个系统函数session_set_save_handler():用来使用外部用户定义的函数,来取代session系统本身的函数。

bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid [, callable $validate_sid [, callable $update_timestamp ]]] )

open(string $savePath, string $sessionName)

open 回调函数类似于类的构造函数, 在会话打开的时候会被调用。 这是自动开始会话或者通过调用 session_start() 手动开始会话 之后第一个被调用的回调函数。 此回调函数操作成功返回 TRUE,反之返回 FALSE

close()

close 回调函数类似于类的析构函数。 在 write 回调函数调用之后调用。 当调用 session_write_close() 函数之后,也会调用 close 回调函数。 此回调函数操作成功返回 TRUE,反之返回 FALSE

read(string $sessionId)

如果会话中有数据,read 回调函数必须返回将会话数据编码(序列化)后的字符串。 如果会话中没有数据,read 回调函数返回空字符串。

在自动开始会话或者通过调用 session_start() 函数手动开始会话之后,PHP 内部调用 read 回调函数来获取会话数据。 在调用 read 之前,PHP 会调用 open 回调函数。

read 回调返回的序列化之后的字符串格式必须与 write 回调函数保存数据时的格式完全一致。 PHP 会自动反序列化返回的字符串并填充$_SESSION 超级全局变量。 虽然数据看起来和 serialize() 函数很相似, 但是需要提醒的是,它们是不同的。 请参考:session.serialize_handler

write(string $sessionId, string $data)

在会话保存数据时会调用 write 回调函数。 此回调函数接收当前会话 ID 以及 $_SESSION 中数据序列化之后的字符串作为参数。 序列化会话数据的过程由 PHP 根据 session.serialize_handler 设定值来完成。

序列化后的数据将和会话 ID 关联在一起进行保存。 当调用 read 回调函数获取数据时,所返回的数据必须要和 传入 write 回调函数的数据完全保持一致。

PHP 会在脚本执行完毕或调用 session_write_close() 函数之后调用此回调函数。 注意,在调用完此回调函数之后,PHP 内部会调用 close回调函数。

Note:

PHP 会在输出流写入完毕并且关闭之后 才调用 write 回调函数, 所以在 write 回调函数中的调试信息不会输出到浏览器中。 如果需要在 write 回调函数中使用调试输出, 建议将调试输出写入到文件。

destroy($sessionId)

当调用 session_destroy() 函数, 或者调用 session_regenerate_id() 函数并且设置 destroy 参数为 TRUE 时, 会调用此回调函数。此回调函数操作成功返回 TRUE,反之返回 FALSE

gc($lifetime)

为了清理会话中的旧数据,PHP 会不时的调用垃圾收集回调函数。 调用周期由 session.gc_probabilitysession.gc_divisor 参数控制。 传入到此回调函数的 lifetime 参数由 session.gc_maxlifetime 设置。 此回调函数操作成功返回 TRUE,反之返回 FALSE

create_sid()

当需要新的会话 ID 时被调用的回调函数。 回调函数被调用时无传入参数, 其返回值应该是一个字符串格式的、有效的会话 ID。

因此我们要准备六个函数。

代码实现:

<?php

//session入库

//以为修改session机制必须借助session_set_save_handler()函数,该函数需要6个可以调用的回调函数,因此需要创建6个人对应的函数

//1.开启session机制
function sess_open() {
    //开启资源
    //连接数据库
    mysql_connect('localhost','root','');
    mysql_query('set names utf8');
    mysql_query('use session');
    echo FUNCTION,'<br/>';
}

//2.关闭session
function sess_close() {
    //关闭资源
    mysql_close();
    echo FUNCTION,'<br/>';
}

//3.读取session
function sess_read($sess_id) {
    //从数据库读取数据
    //根据sess_id(由系统提供)获取数据
    //读数据时要过滤掉过期的数据
    $expire=time()-ini_get('session.gc_maxlifetime');
    $sql="selsct * from session where sess_id='{$sess_id}' and sess_expire>='{$expire}'";
    res=mysql_query(sql);

    //得到的是一个数组
    if(sess=@mysql_fetch_assoc(res)) {
        //得到一个序列化后的字符串
        //要进行反序列化,read只负责读取数据,不负责加工数据
        return $sess['sess_info'];
    }
    echo FUNCTION,'<br/>';
}

//4.写入session
function sess_write(sess_id,sess_info) {
    //向数据库中写入数据
    $time=time();
    $sql="replace into session values('{$sess_id}','{$sess_info}','{$time}')";
    mysql_query($sql);
    echo FUNCTION,'<br/>';
}

//5.销毁session
function sess_destroy($sess_id) {
    $sql="delete from session where sess_id='{$sess_id}'";
    return mysql_query($sql);
}

//6.回收session
function sess_gc() { 
    //从数据库删除过期的session数据
    //判断session是否过期,过期的删除
    $expire=ini_get('session.gc_maxlifetime');
    //得到最迟的时间,在$expire之前的都是过期的
    expire=time()-expire;
    $sql="delete from session where sess_expire < '{expire}'";
    return mysql_query($sql);
}

//使用session_set_save_handler()修改session机制
session_set_save_handler('sess_open','sess_close','sess_read','sess_write','sess_destroy','sess_gc');

//想要使用session,必须要用session_start()来开启
session_start();

$_SESSION['name']='wangqixing';

$_SESSION['age']='23';

//session_destroy();

更新日志

  • 2022-04-03 增加 Cookie 的描述,并且优化排版

Cookie

我们都知道 HTTP 是无状态的,当 HTTP 请求处理完成后,它就不在“认识”发起请求的客户端了,如果 Web 服务器只是用来管理静态文件还好说,对方是谁并不重要,把文件从磁盘读出来发走就可以了。但随着 HTTP 应用领域的不断扩大,对“记忆能力”的需求也越来越强烈。比如网上论坛、电商购物,都需要“看客下菜”,只有记住用户的身份才能执行发帖子、下订单等一系列会话事务。

HTTP 的 Cookie 机制也是一样的道理,既然服务器记不住,那就在外部想办法记住。相当于是服务器给每个客户端都贴上一张小纸条,上面写了一些只有服务器才能理解的数据,需要的时候客户端把这些信息发给服务器,服务器看到 Cookie,就能够认出对方是谁了。

Cookie 的工作过程

2023-03-24T05:19:35.png

当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的身份标识数据,格式是“key=value”,然后放进 Set-Cookie 字段里,随着响应报文一同发给浏览器。

浏览器收到响应报文,看到里面有 Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器。

因为第二次请求里面有了 Cookie 字段,服务器就知道这个用户不是新人,之前来过,就可以拿出 Cookie 里的值,识别出用户的身份,然后提供个性化的服务。

不过因为服务器的“记忆能力”实在是太差,一张小纸条经常不够用。所以,服务器有时会在响应头里添加多个 Set-Cookie,存储多个“key=value”。但浏览器这边发送时不需要用多个 Cookie 字段,只要在一行里用“;”隔开就行。

session技术

Session基础

PHP有内建的会话支持,帮助你维护所有cookie的状态来通过不同的页面和多访问提供持久的变量访问,会话可以让你简单地创建多页面表单(如购物车),页面之间的用户认证信息保存,以及在站点上存储持久的用户偏好。

每个初次访问的客户会分配到一个唯一的会话ID,默认情况下,会话ID会存储到cookie中,叫做PHPSESSID。如果浏览器不支持cookie或者关闭了

创建Session

在创建Session之前,我们需要知道,

3,保存session信息

index1.php

index2.php

  先在网页中运行index1.php,再运行index2.php页面输出:

2023-03-24T05:24:44.png

session可以保存多种数据类型

session不但保存字符串,还可以保存整型,布尔型,数组,对象等。

index1.php

index2.php

  先在网页中运行index1.php,再运行index2.php页面输出:

2023-03-24T05:25:01.png

2023-03-24T05:25:16.png

获取session信息

直接获取所有session

<?php
session_start();
var_dump($_SESSION);

根据key获取

直接获取某个变量

session_start();
echo $_SESSION['name'];

获取数组

2023-03-24T05:30:32.png

获取对象,session在保存对象时候,没法保存类的信息,因此在获取对象,需要先声明这个类。可以把类单独作为一个文件,存储和读取session时候分别引用这个文件。

2023-03-24T05:30:50.png

6,Session的更新,就是根据key值重新保存session的值。

Session的删除

(1) 指定删除session中某个键值对

<?php
session_start();
unset($_SESSION['name']);

(2) 删除所有session

<?php
session_start();
session_destroy();

设置默认时间

session数据默认存在时间是1440s(24分钟),可以在php.ini中修改, session.gc_maxlifetime = 1440。Session文件的存放路径是可以修改的,可以通过修改php.ini改变sesion文件存放路径,session.save_path = "tcp://127.0.0.1:11211"。

自动开启Session

Session使用前,先进行初始化,session_start();这样比较麻烦,可以在php.ini设置session自动初始化,session.auto_start = 0(此方法不推荐)。

10,浏览器访问页面a.php时候,服务器产生一个session文件,将其存放在服务器,同时将session_id发送给浏览器,浏览器将其保存到cookie,浏览器再次访问b.php时候,从cookie中获取session_id发送到服务器,服务器根据session_id获取相应session内容。

问题:如果浏览器禁用cookie,怎么使用session呢?

使用URL重写的方式,url重写分为手动和自动。自动重写url就是配置php.ini,开启透明的SID,其他程序不变,自动重写url不安全,不建议使用。

开启透明SID,需要修改的php.ini是:

session.use_trans_sid = 1  //由0改为1

session.use_only_cookies = 0  //是否只使用cookie来保存session值  该参数为1时,上述机制失效。

session.use_cookies = 0  //设置客户端是否使用cookie来保存session值  该参数的值不影响上述机制的进行。这个可改可不改

手动模式:

index1.php

index2.php

而自动模式,会将url后面自动添加PHPSESSID参数,所以在index1.php中去掉SID即可,index2.php不变。

index1.php

11,php.ini中关于session和cookie的配置

  1. session.use_trans_sid = 0,开启后,默认为每个url后添加了session_name=session_id。
  2. session.save_path=”c:/mysession”,save_path是session文件在服务器的存放路径。
  3. session.gc_maxlifetime = 1440,session默认最大生命周期,当session文件在1440s后没被访问的话,则该session被视为“垃圾文件”,并且等待gc(垃圾回收)进程的调用时候被清理掉;
  4. session.gc_probability=1;session.gc_divisor=1000;这两个参数根据网站规模合理设置。每当初始化一个session时候,有gc_probability/gc_divisor的概率执行一次垃圾回收。

我开启三个会话,则创建三个对应的session文件,当每个文件在30秒内都没被调用的话,就会被当成是“垃圾文件”,等到gc进程调用的时候,“垃圾文件”就会被unlink,因为之前我已经通过修改php.ini配置文件,将gc被调用的概率改成百分百,所以接下来,如果我重新使用任何一个浏览器刷新下页面的时候,三个session文件,应该只剩下一个了。

2023-03-24T05:26:02.png

2023-03-24T05:25:42.png

(4) session.cookie_lifetime,以秒数指定了发送到浏览器的cookie的生命周期,值为0表示“直到关闭浏览器”。默认为0。这个与程序中setCookie(“name”,”zhangsan”,time()+60);类似。

禁用cookie的session使用方案

在浏览器中,有的用户会禁用掉cookie(现在基本上已经不会出现了),但是这种情况还是会有发生。

  • 通过URL传值,把session id附加到URL上(缺点:整个站点中不能有纯净静态页面,因为纯静态页面的session id将无法传递到下一个页面)。
  • 通过隐藏表单,把session id放到表单的隐藏文本框中同表单一块提交过去(缺点不适用\<a\>标签这种直接跳转的非标单情况)
  • 直接配置php.ini,将session.use_trans_sid = 0设置为1
  • 用文件、数据库等形式保存session id,在跨页面中手动调用。