会话技术

概述:HTTP 协议是无状态的协议。当一个用户请求一个页面之后再请求另外一个页面时,HTTP 无法知道这两个请求时来自同一个用户。所以就需要会话技术来记录用户的状态

会话技术是维护同一个浏览器与服务器之间多次请求数据状态的技术。PHP 中常用的会话技术有 CookieSession 两种,

  • Cookie:在浏览器端存储数据并以此来跟踪和识别用户的机制
  • Session:将信息存放在服务器端的会话技术

Cookie

当用户通过浏览器访问 Web 服务器 时,服务器会给用户发送一些信息,这些信息都保存在 Cookie 中。当浏览器再次服务器的时候,会在请求头中同时将 Cookie 发送给服务器。

利用 Cookie 可以跟踪用户与服务器之间的会话状态,通常应用于保存浏览历史、保存购物车商品和保存用户登录状态等场景。

当用户第一次访问服务器时,服务器会在响应消息中增加 Set-Cookie 头字段,将信息以 Cookie 的形式发送给浏览器,保存到浏览器缓冲区。下次访问的时候再将 Cookie 发给服务器,这样服务器就识别出了是哪个用户在发送请求。(关于 Cookie 的一些知识在文章底部)

Cookie 的一些缺点:

  • 附加在 HTTP 消息中,增加了数据流量
  • 在 HTTP 中是以明文传输的,安全性不高,容易被窃取
  • Cookie 存储于浏览器,可以被篡改,服务器接收后必须验证数据的合法性
  • 浏览器限制 Cookie 的数量和大小(一般是 50 个,一个不超过 4KB)

① 创建 Cookie

在 PHP 中使用 setcookie() 函数可以创建和修改 Cookie。其声明方式如下:

1
2
3
4
5
6
7
8
9
bool setcookie(
string $name, // Cookie 的名字(必选)
string $value = "", // Cookie 的值
int $expire = 0, // Cookie 的有效期
string $path = "", // Cookie 在服务器端的路径
string $domain = "", // Cookie 的有效域名
bool $secure = false, // 指定是否通过安全的 HTTPS 连接来传输
bool $httponly = false // 指定 Cookie 只能通过 HTTP 协议访问
)

实现代码

1
setcookie('abc', '123', time() + 1800)

​ 第三个参数是时间戳,省略的时候,Cookie 会在浏览器结束的时候关掉

如果使用 JavaScript 操作 Cookie,setcookie() 的 $httponly 的值要设置为 false

② 获取 Cookie

在 PHP 中,任何从客户端发送的 Cookie 数据都会被自动存入到 $_COOKIE 超全局数组( $_COOKIE[] )变量中,以此来获取 Cookie 数据

1
2
3
4
5
6
7
8
9
setcookie('name', '张三');
echo $_COOKIE['name'];
// 保存和获取数组形式的Cookie
setcookie('history[one]', 1);
setcookie('history[two]', 2);
$history = isset($_COOKIE['history']) ? (array)$_COOKIE['history'] : [];
foreach ($history as $k => $v) {
echo "$k - $v <br>"
}

当 PHP 第一次使用 setcookie() 来创建 Cookie 时,$_COOKIE 中没有这个数据,当浏览器下次请求的时候才可以通过 $_COOKIE 获取到值

③ 删除 Cookie

如果没设置过期时间,当浏览器关闭的时候 Cookie 文件就会被清除。如果想自定义删除:

1
setcookie('data', '', time() - 1);

表示立即过时,相当于删除了。value 设置为空,过期时间设置为小于当前系统时间。

Chrome 浏览器中,设置 > 高级设置 > 隐私设置 > 内容设置 > 所有 Cookie 和网站数据

不同版本的浏览器可能不同,不同浏览器能够存放的数量也可能不同

超全局变量 $_REQUEST

默认情况下,$_REQUEST 超全局变量数组变量,可以同时获取用户 GET 和 POST 请求方式提交的数据

1
2
3
4
<form method="post" action="test.php?a=xxx">
<input type="text" name="b" value="yyy">
<input type="submit" value="提交">
</form>

在 test.php 中

1
print_r($_REQUEST);

$_REQUEST 的值受 php.ini 文件中的 request_order 影响

1
request_order = "GP"
  • G 表示$_GET,P 表示 $_POST

  • 添加 C($_COOKIE)可以接受 Cookie 数据,PHP 解析的优先级是 C>P>G。接受数据同名的时候,优先级高的会覆盖优先级低的

    • PHP 解析优先级也可以改,同样在 php.init 文件中,修改

      1
      variable_order = "PG"
      • 后解析的新值会覆盖旧值

Session

Session 在网络应用中称为会话,通常用于保存用户登录状态、保存生成的验证码等

Session 是一种服务器端的技术,它的生命周期从用户访问页面开始,知道断开与网站的连接结束。Web 服务器在运行时会为每个用户的浏览器创建供其一个独享的 Session 文件

每一个 Session 都具有一个唯一的会话 ID,用于标识不同的用户。会话 ID 会分别保存在客户端和服务器端,客户端通过 Cookie 保存,而服务器端以文件的形式保存在 php.ini 指定的 Session 目录(Windows 默认保存:C:Windows\Temp)

Session 的基本使用

① 启动

使用 session_start() 启动,该函数返回值为布尔值,成功返回 true,失败 false

可在浏览器中 Network 中 点中某一条响应查看它的 Session

② 使用

可以通过 $_SESSION 添加、读取、修改 Session 中的数据

1
2
3
4
5
6
7
8
9
session_start();
$_SESSION['username'] = 'Tome';
$_SESSION['id'] = [1, 2, 3];
if (isset($_SESSION['test'])) {
$test = $_SESSION['test'];
}
unset($_SESSION['username']); // 删除单个数据
$_SESSION = []; // 删除所有数据
session_destroy(); // 结束当前会话
  • $_SESSION = [],可以删除 Session 中的所有数据,但是 _Session 文件还是存在的_,它是一个空文件
  • 如果要删除这个空文件,使用session_destroy()

③ Session 的配置

配置项 含义
session.name 指定 Cookie 的名字,只能由字母、数字组成,默认为 PHPSESSID
session.save_path 读取或设置当前会话文件保存路径,默认为“C:Windows\Temp”
session.auto_start 指定是否在请求开始时自动启动一个会话,默认值是 0(不启动)
session.cookie_lifetime 以秒数指定发送到浏览器的 Cookie 的生命周期,默认为 0(知道浏览器关闭)
session.cookie_path 设定会话 Cookie 的路径,默认为 /
session.cookie.domain 设定会话 Cookie 的域名,默认为没有
session.cookie_secure 是否仅通过安全连接发送 Cookie,默认 off
session.cookie_httponly 是否仅通过 HTTP 访问 Cookie,默认 off

PHP7.0 开始,在 session_start() 中可以对 Session 进行配置,用于覆盖 php.ini 中对 Session 的配置指令

1
session_start(['name' => 'MySESSID']);
  • 在这里配置的时候,键名不需要加上 session.
  • 在这里配置的时候,session.cookie_lifetime(整形),session.auto_start、session.cookie_secure 和 session.cookie_httponly 为布尔型,其它的是字符串型

session_start() 中的配置只在脚本运行周期中生效,并不影响 php.ini 中原本的配置

Session 机制

Session 机制是通过调用 session_set_save_handler 实现的

默认情况下,PHP 中的 Session 是通过实现 SessionHandlerInterface 接口,将其以文件的形式存储在服务器中的,这个接口有 6 种抽象方法:

  • close:关闭 Session
  • destroy:销毁 Session
  • gc:垃圾回收
  • open:开启 Session
  • read:读取 Session
  • write:写入 Session
1
2
3
4
5
6
7
8
9
interface SessionHandlerInterface
{
public function close();
public function destroy(string $session_id);
public function gc(int $maxlifetime);
public function open(string $save_path, string $session_name);
public function read(string $session_id);
public function write(string $session_id, string $session_data);
} default charset=utf-8;

通过重新实现接口,完成具体的实现方法,然后再利用 Session 机制可以改变 Session 存储的默认方式

例:Session 入库实现例子

① 先创建一个保存 Session 的数据表

1
2
3
4
5
6
7
create database `sess_storage`;
use `sess_storage`;
create table `session` (
`id` varchar(255) primary key comment 'SessionID',
`expires` int unsigned not null comment '过期时间',
`data` blog comment '数据'
)

② 实现接口

创建 SessionDB.php 文件,用于实现接口

1
2
3
4
5
6
7
8
9
10
class SessionDB implements SessionHandlerInterface
{
private $link;
public function open($savePath, $sessionName) { /*处理语句*/ };
public function close() { /*处理语句*/ };
public function write($id, $data) { /*处理语句*/ };
public function read($id) { /*处理语句*/ };
public function destroy($id) { /*处理语句*/ };
public function gc(int $maxlifetime) { /*处理语句*/ };
}
  • $link 用于保存数据库连接

实现 open 方法

  • 接口里面的 open() 方法类似于类的构造方法,在会话打开的时候内调用
1
2
3
4
public function open($savePath, $sessionName) {
$this -> link = new mysqli('localhost', 'root', '123456', 'sess_storage');
return (bool) $this -> link;
}

实现 close 方法

  • 这类似于类的析构方法,在 write 方法调用完成之后调用。这里实现关闭数据库连接
1
2
3
public function close() {
return $this -> link -> close();
}

实现 write 方法

  • 将 Session 数据写入指定的位置,默认的情况下以文件的形式写进服务器中。以此方法修改保存到数据库中
1
2
3
4
5
6
7
public function write($id, $data) {
$expires = time() + 3600; // 一小时的过期时间
$sql = 'REPLACE INTO `session` SET `id` = ?, `data` = ?';
$stmt = $this->link->prepare($sql);
$stmt = bind_param('sis', $id, $expires, $data);
return (bool) $stmt->execute();
};
  • Session 数据表中的 ID 是 Session 的会话 ID,是唯一的
  • 在有效的时间内重复修改 Session 数据,使用 REPLACE

实现 read 方法

  • 用于根据 Session 的会话 ID 到指定位置获取 Session 数据并返回执行结果
1
2
3
4
5
public function read($id) {
$now = time();
$sql = "SELECT `data` FROM `session` WHERE `id` = '$id' AND `expires` > $now";
return (string) $this->link->query($sql)->fetch_assoc()['data'];
}
  • 存在时直接返回结果,不存在返回空字符串

实现 destroy 方法

  • 实现用于结束当前会话的同时,根据会话 ID删除数据库中对应的 Session 信息
1
2
3
4
public function destroy($id) {
$sql = "DELETE FROM `session` WHERE `id` = $id";
return (bool) $this->link->query($sql);
}

实现 gc 方法

  • 用于清理会话中的旧数据
1
2
3
4
5
public function gc(int $maxlifetime) {
$sql = "DELETE FROM `session` WHERE (`expires` + $maxlifetime) < $maxlifetime";
$result = $this->link->query($sql);
return (bool) $result;
}
  • $maxlifetime 保存的是 php.ini 中 session.gc_maxlifetime 配置项指定的 Session 数据经过多长时间被视为垃圾而清除的秒数

完成 Session 入库设置

  • 实现所有的抽象方法之后,利用 SessionDB 类的构造方法设置 Session 机制
1
2
3
public function __construct() {
session_set_save_handler($this, true);
}
  • 第一个参数:实现 SessionHandlerInterface 接口的对象
  • 第 二个参数:true 表示将 session_write_close() 设置为 php 终止是执行的函数

测试 Session 入库

  • 新建 test.php 文件,引入 SessionDB.php 文件,测试 Session 数据是否保存在数据库中
1
2
3
4
require './SessionDB.php';
new SessionDB(); // 配置Session入库
session_start();
$_SESSION['test'] = 'content';
  • 到数据库中查看到:test|s:7:"content
    • test:Session 数据对应的 key 值
    • | 后 的 “s:7” 表示该数据是 7 为字符长度的字符串类型数据
    • content:Session 数据

题外话

[]( ̄ ▽  ̄)*

  • Cookie :是一个请求头,其中含有先前由服务器通过Set-Cookie响应头投放并存储到客户端的 HTTP cookies
  • Set-Cookie :响应头,用来由服务器端向客户端发送 cookies
  • HTTP Cookie :也叫 Web Cookie 或者 _浏览器 Cookie_。是由服务器发送到用户浏览器并保存在本地的一小块数据(上面介绍的 Cookie 是这个)

MDN 中是这样写的,在 MDN 中它们的目录关系是:

  • Cookie :HTTP > HTTP Headers > Cookie
  • Set-Cookie :HTTP > HTTP Headers > Set-Cookie
  • HTTP Cookie :HTTP > HTTP cookies

所以,应该是这样的:

Cookie 和 Set-Cookie 在 Web 技术中是属于 Header,而 HTTP Cookie,是 Cookie 的请求内容,是 Set-Cookie 的响应内容

在浏览器请求中就可以看到 Cookie 和 后面一大串字符串的 HTTP cookie

相关资料:MDN

作者

dsjerry

发布于

2020-07-21

更新于

2023-09-15

许可协议

评论