前言:
在不久以前,本人去参加了某公司的实习面试,其中面试官问我关于 SESSION 实现的原理,当时我就懵逼了,由于在以前的开发中,我只知道 session 与 cookie 的区别在于:session 是保存在服务器端,cookie 保存在客户端。那 session 在服务端是怎么样保存的?session_id 又是什么?等等。我当时答不上来。回来后决定把这些搞懂。php
为何要使用 SESSION?
是由于目前网络中所使用的http协议形成的,http协议是无状态协议,通俗点说就是当你发送一次请求道服务器端,而后再次发送请求到服务器端,服务器是不知道你的这一次请求和上一次请求是来源于同一我的发送的。而 session 就能很好解决这个问题。面试
在咱们的访问期间,各个页面间共享的数据放在session中,就好比说咱们的登录信息,若是没有 session 的话,当你在这个页面登录以后,在点击下一个页面的时候你须要再次登录。redis
引入:
如今咱们来看看平时咱们是怎么使用 session 的,你们看下面的例子数据库
<?php #index.php 文件 session_start(); //启动会话 $user = isset($_GET['user'])?$_GET['user']:"default" if(!isset($_SESSION['user'])){ $_SESSION['user'] = $user; //设置会话 } var_dump($_SESSION); unset($_SESSION['user']); //清除会话
如今咱们在浏览器 A 打开ubuntu
http://localhost/index.php?user=lsgogroup;
返回:vim
array(1){["user"]=>string(9)"lsgogroup"}
在浏览器 B 打开数组
http://localhost/index.php
返回:浏览器
array(1){["user"]=>string(7)"default"}
问题:缓存
session_start() 的做用是什么?
为何在浏览器 B 中返回的不是:服务器
array(1){[“user”]=>string(9)“lsgogroup”} ?
$_SESSION 数组是怎么保存这些数据的?
理解 PHP SESSION 机制:
session 机制是一种服务器端的机制,服务器使用一种相似于散列表的结构来保存信息。
当程序须要为某个客户端的请求建立一个 session 的时候,服务器首先检查这个客户端的请求(Http Request)里是否已包含了一个 session 标识-称为 sessionid,若是已包含一个 sessionid 则说明之前已经为此客户端建立过 session,服务器就按照 sessionid 把这个 session 检索出来使用,若是客户端请求不包含 sessionid,则为此客户端建立一个 session 而且生成一个与此 session 相关联的 sessionid,sessionid的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 sessionid 将被在本次响应中返回给客户端保存。而这个 sessionid 就是做为客户端的惟一标识而存在的(即便在同一台电脑上,浏览器 A 和浏览器 B 对于服务器来讲都是不一样的客户端)。
上面一段话你可能暂时不会理解,不过没关系,我会在下面做出解释:
如今咱们来看看浏览器 A 和 浏览器 B 的 cookie:
浏览器 A (这里对应是谷歌浏览器):
浏览器 B (这里对应是火狐浏览器) :
对比能够看到,两个浏览器对于 localhost 都有一条名为 PHPSESSID 的 cookie 记录,而这个 PHPSESSID 就是上面所说的 sessionid,它告诉服务器请求是来自浏览器 A 仍是浏览器 B 。
如今咱们能够回答上面的问题 2 了:
因为浏览器 A 的 PHPSESSID 和浏览器 B 的 PHPSESSID 是不同的,所以服务器根据 sessionid 检索 session 的数据也是不同的,也就是说浏览器 A 请求的 $_SESSION 数组和 浏览器 B 请求的 $_SESSION 数组也是不同的。
(固然,PHPSESSID 这个 id 名不是固定的,咱们能够在 php.ini 文件中的 session.name 项进行修改。)
上面的例子是使用 COOKIE 保存 PHPSESSID,可是,因为 cookie 能够被人为的禁止,必须有其余机制以便在 cookie 被禁止时仍然可以把 sessionid 传递回服务器。有两种技术能够解决这个问题:
URL重写,就是把 sessionid 直接附加在URL路径的后面:
http://localhost/index.php?user=lsgogroup&PHPSESSID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng
隐藏表单传递。
因为这不是重点,这里不展开讲。
SESSION 是怎么存储数据的?
答:session 是以文件的形式保存的。
php.ini 中的配置项 session.save_handler = files;
默认为 file,定义 session 在服务端的保存方式,file 意为把 session 保存到一个临时文件里。
php.ini 中的配置项 session.save_path= “”;
这个里面填写的路径,将会使session文件保存在该路径下。
session 文件的命名格式是:“sess_[PHPSESSID的值]”。每个文件,里面保存了一个会话的数据。
咱们查看服务器端 session.save_path 目录会发现不少相似 sess_vv9lpgf0nmkurgvkba1vbvj915 这样的文件,这个其实就是 sessionid(也就是 PHPSESSID) “vv9lpgf0nmkurgvkba1vbvj915″ 对应的数据。真相就在这里,客户端将 sessionid 传递到服务器,服务器根据 sessionid 找到对应的文件,读取的时候对文件内容进行反序列化就获得 session 的值($_SESSION数组中的数据),保存的时候先序列化再写入。
因为我作实验的时候使用的是 ubuntu 系统,所以个人 session.save_path 默认实在 /var/lib/php/sessions 下,咱们来看看前面浏览器 A 生成的 session 文件是怎样的(浏览器 A 的 PHPSESSID = ‘nqqleletmsb0nuf7d4ulvotk45’):
cd /var/lib/php/sessions #因为session数据是很重要的数据,所以必须只能 root 用户才能打开 sudo vim sess_nqqleletmsb0nuf7d4ulvotk45 #看看文件格式是否是 "sess_[PHPSESSID的值]"
文件内容:
user|s:9:"Jodieeeee";
从文件内容能够看到,数据是通过序列化的,数据的读取规则是这样的:
每个session的值是以分号";"分开的。好比”user|s:9:“Jodieeeee”;“就是一个完整的session值结束,若是再添加 $_SESSION[‘name’]=“LSGOZJ”,则变成这样 ”user|s:9:“Jodieeeee”;name|s:7:“LSGOZJ”;“
里面的读取规则:符号“|”前面表示 session 名称。符号后面是该 session 的具体信息。包括:数据类型,字符长度,内容。好比说 ”user|s:9:“Jodieeeee”;“,$_SESSION[‘user’] 的值是 “Jodieeeee”,是一个长度为 9 的字符串。
等等。。。
到了这里,咱们就解决了上面的问题 3 了。
其实还有不少种存储session的方式,若是咱们想自定义别的方式保存(好比用数据库),则须要把该项设置为 user;咱们还可使用 memcache、redis 等优秀的缓存系统(前提是你的服务器安装了此类软件)。
session_start()函数的做用是什么?
了解的原理以后,所谓的 session 其实就是客户端一个 sessionid 对应服务器端一个 session file,新建session 以前执行 session_start() 是告诉服务器要种一个 cookie 以及准备好 session 文件,要否则你的session 内容怎么存;读取 session 以前执行 session_start() 是告诉服务器,赶忙根据 sessionid 把对应的 session 文件反序列化。
说白了,当咱们使用 php 的内置函数 session_start( ) 的时候,就是到服务器的指定的磁盘目录把 session 数据载入,实际上就是拿相似 sess_74dd7807n2mfml49a1i12hkc45 的文件。
只有一个 session 函数能够在 session_start() 以前执行,session_name():读取或指定 session 名称(好比默认的就是”PHPSESSID”),这个固然要在session_start以前执行。
根据 http 的请求机制,当浏览器请求的时候,头部信息会把浏览器中的 cookie 一块儿发给服务器。PHPSESSID 这个 cookie 也是在其中发给了服务器,php 引擎经过读取 PHPSESSID 的值来肯定要载入哪一个 session 文件。
好比值为 74dd7807n2mfml49a1i12hkc45,载入的就是"sess_74dd7807n2mfml49a1i12hkc45"。
注:当你调用 php 的函数 session_start(),才代表你须要使用 session 文件了。否则无缘无故就去载入文件,浪费性能。
SESSION 的清理:
在平时咱们谈论 SESSION 的机制的时候,经常听到这样一种误解“只要关闭浏览器,session就消失了”(本人也是一度认为这样),其实能够想象一下会员卡的例子,除非顾客主动对店家提出销卡,不然店家绝对不会轻易删除顾客的资料。
对 session 来讲也是同样的,除非程序通知服务器删除一个 session,不然服务器会一直保留,程序通常都是在用户作 logoff (注销操做,相似于 session_destroy()操做)的时候发个指令去删除 session。然而浏览器历来不会主动在关闭以前通知服务器它将要关闭,所以服务器根本不会有机会知道浏览器已经关闭,之因此会有这种错觉,是大部分 session 机制都使用会话 cookie 来保存 sessionid ,而关闭浏览器后这个sessionid就消失了,再次链接服务器时也就没法找到原来的session,可是服务器上对应的 session file 依然存在。
为何关闭浏览器后 sessionid 就会消失呢?这跟 cookie 在客户端的存储有关,若是在设置 cookie 的时候没有指定生命周期,那么 cookie 的数据是存储在内存中的,当浏览器被关闭,内存被回收了,那么cookie 也就没有了(这就是为何cookie在没有指定生命周期的时候,其生命周期与浏览器生命周期同样)。
若是服务器设置的 cookie 被保存到硬盘上(设置了生命周期),或者使用某种手段改写浏览器发出的HTTP请求头,把原来的 sessionid 发送给服务器,则再次打开浏览器仍然可以找到原来的session。
偏偏是因为关闭浏览器不会致使 session 被删除,迫使服务器为 seesion 设置了一个失效时间,当距离客户端下一次使用 session 的时间超过这个失效时间时,服务器就能够认为客户端已经中止了活动,才会把session 删除以节省存储空间。
咱们来看看服务器是怎样删除 session 数据的:
session.gc_probability = 1 session.gc_divisor = 100 session.gc_maxlifetime = 1440
这三个配置项组合构建服务端 session 的垃圾回收机制。
session.gc_probability 与 session.gc_divisor 构成执行 session 清理的几率,理论上的解释为服务端按期有必定的几率调用 gc(garbage collection 垃圾回收) 进程来对 session 进行清理,清理的几率为:gc_probability/gc_divisor 好比:1/100 表示每个新会话初始化时,有 1% 的几率会启动垃圾回收程序,清理的标准为 session.gc_maxlifetime 定义的时间(清理过时的数据)。
我所用的系统是ubuntu,php.ini 中指定的 session.gc_probability = 0,也就是几率为零,缘由是该系统是使用 cron 脚原本执行垃圾清理的。
后话:
session 还有不少须要整理和学习的地方,如:
session多服务器共享的问题,假若有多台php服务器进行负载均衡的时候,用户登陆时访问的是第一台服务器,没准下一个页面访问的是第二台服务器,可是 session 数据是存储在第一台服务器上的,所以在访问下一个页面的时候因为没有 session 数据(第二台服务器上)致使用户必须从新登录。从上面的分析咱们也知道,php 中 session 默认经过文件的方式实现,可是若是访问量大,可能产生的 SESSION 文件会比较多,从众多的文件中选择其中一个文件不是一件轻松的事情,并且每次都以打开文件、读取文件的方式,也会产生大量的 I/O 操做,严重影响服务器的性能。