session对于很多人来说也许是记住了某些函数和方法,却不知道它的原理。写这篇文章来帮助大家梳理一下吧!

本文可能要求:

  • 你对 session 有初步的理解
  • 本文案例将使用 php 作为后端语言,需要你有一些小基础

当你在做一个网站时,你大概率需要做一个 登录 的功能。

如果你是个初学者,大概率会在网上搜索到一些资料,其中肯定包括,使用Session来记录用户的登录状态

# 一个简单的例子

我们现在拥有一个如下代码所示的 index.php 文件,并且将它正确地部署在服务器上。

<?php
session_start();
if(isset($_SESSION['name'])){
	echo $_SESSION['name'];
}else{
	echo '你谁啊';
}
$_SESSION['name'] = 'tricky';

这样一串代码,当我们第一次访问这个网页时,网页会输出: 第一次访问index.php

当我们刷新一下网页(也就是第二次访问)之后,网页会输出: 第二次访问index.php

我们得到一个结果,上面的代码第一次访问和第二次访问得到的结果不一样。

当然,第三次、第四次...第n次的结果肯定和第二次访问是一样的。

这个代码固然很容易看懂,你在不运行的情况下也很容易猜测到它具体的效果,但是原理呢?

灵魂拷问:

  • 为什么在使用 $_SESSION 这个超全局数组之前要先执行 session_start() ?
  • $_SESSION 数组能够保存我们的数据,但是这些数据存在哪里了呢?
  • 不同的客户端去访问这个页面,它们从 $_SESSION 中读取出来的数据是相同的吗?

为了解决这些问题,于是乎有了这篇文章。

# HTTP 是无状态协议

每次我们访问一个网页时,实际上是 客户端(也就是浏览器) 向服务器发起一个请求(Request),服务器接收到这个所谓的请求之后,会在服务器上读取正确的资源来响应(Response)、发送回给客户端。

客户与服务员

这些请求呢,从人类视角上来说就只是一句话:我要XXX。

而从计算机的角度,我们必须约定请求的格式到底是什么样的,这样子服务器才能理解你在说什么。这种所谓的格式,或者说为了解决沟通问题而产生的约定,我们称之为 协议

而我们的浏览器与网页服务器之间进行沟通的时候,一般遵循的是 HTTP协议

这些协议看起来像是这样子:

客户与服务员2

而正是因为这种协议,导致了一个问题,观察以下现象:

我们在一个客户端上向服务器发起两次相同的请求: 同一个客户端发起两次请求

然后在两个客户端上发起两个相同的请求: 不同的客户端发起两个相同的请求

由于请求的报文中,并没有包含任何与发送方有关的信息。

所以对于服务器而言,它无法识别这些请求到底是来源于哪个客户端

换句话说,即使同一个客户端发送的多次请求,服务器也无法确定这些请求就是来源于同一个客户端。 服务器不知道请求来自于哪里

针对这种特点,我们称HTTP协议是一种无状态的协议,它无法将上一次访问和下一次访问进行关联。

但是聪明的人们总是有办法解决问题。

如果每一次请求当中,不同的客户端就携带不同的信息,让服务器程序根据这些信息来识别你到底是谁,这样不是很棒吗? 携带上身份信息

我们甚至可以很快地写出如下的代码来识别请求是来源于哪个客户端的:

<?php
if($_COOKIE['who']==='A'){
    echo "你是A";
}else if($_COOKIE['who']==='B'){
    echo "你是B";
}

在前端页面中,我们可以简单地通过以下代码来创建 Cookie

document.cookie="who=A";

Cookie 创建完成后,每一次发送请求时,浏览器都会自动将这些 Cookie 夹带在请求报文中

这样一来,我们竟然让HTTP协议看起来变得不那么 无状态 了。

# session_start()

现在我们回过头来看看 php 中的 session_start() 到底做了什么。

默认情况下,当我们执行这个函数时,服务器做了以下事情:

  1. 生成了一串数字
  2. 利用这串数字在服务器上创建了一个文件
  3. 将这串数字发送给客户端,命令客户端将这串数字设置为 Cookie

第二点我们先忽略,先看第三点。在 HTTP 协议中,响应报文中有一个头部叫做 Set-Cookie,当浏览器接收到的响应报文中含有 Set-Cookie 时,便会根据它的值来为当前站点设置一个 Cookie

Set-Cookie

如上图所示,未来的所有请求,客户端都将携带上服务器命令它设置的 Cookie。

所以,上文中所说的,服务器生成了一串数字,指的是 7 e1bec958358822f954ec464 3573569e 这串数字。这串数字完全随机,毫无规律可循。

当客户端携带着这串数字作为 Cookie 时,服务器就能够识别这个请求到底是来自于哪一个客户端啦~

大家也可以动手写一个 session_start() ,然后对该页面进行访问,打开浏览器的开发者工具,我们就可以看到这个头部信息啦:

set-cookie开发者工具截图

当我们第二次访问该页面时,请求头部中也携带了该 cookie 啦,截图如下: cookie开发者工具截图

上文中说的第二步:session_start() 执行时,会在服务器上创建一个文件:

session创建的文件

这个文件位于什么地方并不重要,重要的是我们发现了一些规律:这些文件似乎都是以sess开头,并且后面附上的一串数字便是上文第一步中服务器生成的那串数字!

所以,我们称它为 session文件 吧!

这个session文件实际上是用来给你记录这个客户端的信息的。例如,

<?php
session_start();
$_SESSION['name'] = "tricky";

像这样一段代码,会在这个创建的文件中记录一个键值对信息 name=tricky

当客户端下次访问时,由于请求中携带了 cookie,服务器拿到 cookie 中的这串数字,便可以找到相对应的 session 文件,我们就可以取得我们上次记录的数据啦~

所以,session_start() 的工作流程实际上是:

  1. 判断请求中是否携带了 cookie 信息
  2. 如果没有携带cookie,则生成一串数字
  3. 利用这串数字创建一个 session文件
  4. 命令客户端下次记得携带这串数字作为 cookie
  5. 如果携带了cookie,则判断有没有与之相对应的 session文件
  6. 如果没有,就创建一个
  7. 将 session 文件中的数据装载到 $_SESSION 变量上

session流程图

# SESSION

通过 cookie ,客户端让服务器知道了来访的人到底是谁,能够让服务器将多个请求理解为是同一个客户端发送的。

通过 php 中的 $_SESSION,能够让客户端在服务器上存储一些信息。

客户端下次访问的时候,能够通过 cookie 让服务器读取相应的(上次存储的)信息。

这样一来,服务器和客户端互相传递数据就变得像是在聊天一样。

客户端与服务器在聊天

像这样一个过程,我们称之为是会话(Session)

而为了实现会话的功能,php已经给我们提供了简单得不能再简单的方法了。

但是,我们不能够狭义地将session简单地理解为就是使用 session_start()$_SESSION 数组。

广义上讲,session是为了解决HTTP无状态问题的一种思维方案,为了让客户端拥有在服务器上存储一些数据的能力。

实际上,我们也能自己实现一个 session

在 php 中,我们可以通过 setcookie() 方法来设置 HTTP 的 set-cookie 头,于是乎,我们也可以自己生成一串随机的字符串。

<?php
$my_session_id = rand(10000,99999);
setcookie("MY_SESSION", $my_session_id, time()+(60*60*24*30) );

接着,在数据库中创建一个表,用来存储这个id和相关的数据,这个表可以像这样:

session_id name ...
10001 tricky ...
12345 zeeb ...
34532 panda ...

当客户端下一次进行访问时,只要cookie中携带了我们指定的 cookie。我们就可以用以下的伪代码从服务器的数据库中获取到该用户的数据了:

<?php
// 这是伪代码!不能直接运行的
$username = getUserNameFromDatabase($_COOKIE['MY_SESSION']);
echo $username;

这样,我们也手动实现了一个简单的 session

这一个小节的目的是想强调,session是一个概念,具体的实现方式有很多种,而php给我们也提供了一种简单的实现。

正确的认识这个概念,对 token 认证的学习有很大的帮助。

纵观整篇文章,无论是 php 自己默认的 session 实现,还是自己实现的 session,都需要用到 cookie 来传递信息。

那为什么不直接把数据存在 cookie 里面呢?

我们知道,cookie 存储的数据是放在客户端机器中的。这些数据一旦浏览器清空缓存就会不见了,所以风险挺大的。

但是问题又来了,浏览器清空了 cookie 也会把我们用于认证的那串id也给清空掉啊!

借用 session 这种思想之后,我们大可以把数据存在服务器的数据库里面,每一次在用户输入完账号密码之后才给浏览器分配一个用户认证的 cookie,这样一来,用户如果清空了浏览器的 cookie,我们只需要让它重新登录一遍就ok啦~

当然,如果我们的数据很不重要,也不怕用户去清空 cookie,你也可以选择把这些数据存在 cookie 里面。

将数据存在客户端或者服务器取决于现实的业务场景。