前言:前不久去一家公司实习面试,面试官问我SESSION的实现原理。当时一头雾水,因为在之前的开发中,只知道session和cookie的区别是:session保存在server端,cookie保存在client端。服务器端如何保存session?session_id又是什么?等,当时答不上来。回来后,我决定弄清楚。为什么要使用会话?是网络目前使用的http协议造成的。http协议是一个无状态协议。通俗地说,当你向服务器发送请求,然后再次向服务器发送请求时,服务器并不知道你这次的请求。它是由与先前请求相同的人发送的。session可以很好的解决这个问题。在我们访问的过程中,各个页面之间共享的数据都放在session中,比如我们的登录信息。如果没有session,你在这个页面登录后,点击下一页需要重新登录。简介:现在让我们来看看我们平时是如何使用session的。请看下面的例子string(9)"lsgogroup"}在浏览器B中打开http://localhost/index.php返回:array(1){["user"]=>string(7)"default"}问题:session_start()的作用是什么?为什么它不返回:array(1){["user"]=>string(9)"lsgogroup"}在浏览器B中?$_SESSION数组是如何存储这些数据的?理解PHPSESSION机制:会话机制是一种服务器端机制,服务器端使用类似哈希表的结构来保存信息。当程序需要为客户端的请求创建一个会话时,服务器首先检查客户端的请求(HttpRequest)是否已经包含一个会话标识符——称为sessionid。客户端创建了一个session,服务端根据sessionid获取session。如果客户端请求不包含sessionid,则为客户端创建session,并生成与session关联的sessionid。sessionid的取值应该是一个既不重复又不易查找模仿的字符串。这个sessionid会在这个response中返回给client保存。而这个sessionid是作为客户端的唯一标识存在的(即使在同一台电脑上,浏览器A和浏览器B对于服务器来说也是不同的客户端)。上面这段话你可能暂时看不懂,没关系,我在下面解释一下:现在我们来看一下浏览器A和浏览器B的cookies:浏览器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时仍然能够将sessionid传回服务器的机制。解决这个问题有两个技巧:URL重写,就是在URL路径后直接追加sessionid:http://localhost/index.php?user=lsgogroup&PHPSESSID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng隐藏表单发送。由于这不是重点,我不会在这里讨论。SESSION是如何存储数据的?答:会话以文件的形式保存。php.ini中的配置项session.save_handler=files;默认是file,它定义了session在服务器端是如何保存的,file就是把session保存在一个临时文件中。php.ini中的配置项session.save_path="";这里填写的路径会把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用户才能打开sudovimsess_nqqleletmsb0nuf7d4ulvotk45#检查文件格式是否为“sess_[PHPSESSID值]”文件内容:user|s:9:“Jodieeeee”;从文件内容可以看出,数据是序列化的,数据读取规则如下:每个session的值之间用分号“;”隔开分为。例如,“用户|s:9:”Jodieeeee“;”是一个完整会话值的结束。如果加上$_SESSION['name']="LSGOZJ",就会变成这样"user|s:9:"Jodieeeee";name|s:7:"LSGOZJ";"里面的读法:符号"|"表示会话名称。符号后是会话的具体信息。包括:数据类型、字符长度、内容。比如"user|s:9:"Jodieeeee";",$_SESSION['user']的值为"Jodieeeee",是一个长度为9的字符串,等等。.至此,我们已经解决了上面的问题3。其实存储session的方式有很多种。如果我们要自定义其他保存方式(比如使用数据库),需要将此项设置为user;我们也可以使用memcache、redis等优秀的缓存系统(前提是你的服务器安装了此类软件)。session_start()函数的作用是什么?明白原理后,所谓session其实就是client端的一个sessionid对应server端的一个session文件。在创建新session之前执行session_start()是告诉服务器植入cookie和准备session文件,或者说如何保存你的session内容;在读取session之前执行session_start()就是告诉server根据sessionid反序列化对应的session文件。说白了,我们在使用php的内置函数session_start()时,是将session数据加载到服务器指定的磁盘目录下,其实就是一个类似于sess_74dd7807n2mfml49a1i12hkc45的文件。session_start()之前只能执行一个session函数,session_name():读取或指定session名称(例如默认为“PHPSESSID”),当然必须在session_start之前执行。根据http请求机制,当浏览器请求时,header信息会将浏览器中的cookie一起发送给服务器。PHPSESSIDcookie也被发送到服务器,PHP引擎通过读取PHPSESSID的值来决定加载哪个会话文件。例如,如果值为74dd7807n2mfml49a1i12hkc45,则将加载“sess_74dd7807n2mfml49a1i12hkc45”。注意:当你调用php函数session_start()时,说明你需要使用session文件。否则,它会无缘无故地加载文件,浪费性能。SESSION的清理:我们在讲SESSION的机制的时候,经常会听到这样一种误解“只要关闭浏览器,session就消失了”(我也曾经以为是这样),其实你可以想象一下会员卡的例子,除非顾客主动要求店家出售会员卡,否则店家绝不会轻易删除顾客的信息。会话也是如此。除非程序通知服务器删除会话,否则服务器将永远保留它。程序一般会在用户执行注销(注销操作,类似于session_destroy()操作)时发送删除session的命令。但是浏览器在关闭前从来不会主动通知服务器自己将要关闭,所以服务器永远没有机会知道浏览器已经关闭。造成这种错觉的原因是大多数会话机制使用会话cookie来保存sessionid。关闭浏览器后sessionid消失,再次连接服务器时找不到原来的session,但服务器上对应的session文件仍然存在。为什么关闭浏览器后sessionid消失了?这与客户端cookies的存储有关。如果在设置cookie的时候没有指定生命周期,那么cookie的数据是保存在内存中的。当浏览器关闭,内存被回收时,cookie就会消失(这就是为什么不指定生命周期时,cookie的生命周期与浏览器的生命周期相同的原因)。如果将服务器设置的cookie保存到硬盘(设置了生命周期),或者通过某种手段改写浏览器发送的HTTP请求头,将原来的sessionid发送给服务器,那么原来的再次打开浏览器还是可以找到session的。正是因为关闭浏览器不会导致session被删除,迫使服务器为session设置一个过期时间。当客户端下次使用该会话之前的时间超过这个过期时间时,服务器可以认为客户端已经停止活动。该会话将被删除以节省存储空间。我们来看看服务端是如何删除session数据的:session.gc_probability=1session.gc_divisor=100session.gc_maxlifetime=1440这三个配置项组合起来构建服务端session的垃圾回收机制。session.gc_probability和session.gc_divisor构成执行会话清理的概率。理论上的解释是服务器以一定的概率定期调用gc(garbagecollection)进程来清理session。清理概率为:gc_probability/gc_divisor例如:1/100表示??每次初始化新session时,有1%的概率启动垃圾回收程序,清理标准为session.gc_maxlifetime定义的时间(清除过期数据)。我使用的系统是ubuntu,在php.ini中指定session.gc_probability=0,即概率为零,因为系统使用cron脚本进行垃圾清理。后记:session还有很多需要梳理和学习的地方,比如:session多服务器共享问题,如果有多个php服务器做负载均衡,用户登录时访问的是第一台服务器,可能下一个页面访问的是第二台服务器被访问了,但是session数据保存在第一台服务器上,所以访问下一个页面时,用户必须重新登录,因为没有session数据(在第二台服务器上)。通过上面的分析,我们也知道php中的session默认是通过文件实现的,但是如果访问量大的话,SESSION文件可能会比较多。从众多文件中选择一个文件不是一件容易的事,而且每次打开和读取文件也会产生大量的I/O操作,严重影响服务器的性能。
