whatshotPHP session 工作原理展示

@Ta 06-09 21:37 613点击

以下代码通过cookie+文件实现了与PHP自带的session相同的效果,可以用于理解session是怎么实现的。
my_session_start()函数所做的事情和PHP自带的session_start()所做的事情没有什么不同,在session.save_path相同的情况下两者可以互相读取对方生成的session文件。

代码可以在PHP5.4和PHP7中运行。默认session保存文件夹是当前目录(在第四行指定,__DIR__,可以改成其他你想要的值)。

<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
ini_set('session.save_path', __DIR__);

my_session_start();

//-------- session 调试信息 --------
echo '<p>session id: '.my_session_id().'</p>';

echo '<code><pre>';
var_dump($_SESSION);
echo '</pre></code>';

$now = date('H:i:s');
if (isset($_SESSION['last_visit_time'])) {
  echo '<p>上次访问时间: '.$_SESSION['last_visit_time'].'</p>';
}
echo '<p>当前时间: '.$now.'</p>';

$_SESSION['last_visit_time'] = $now;

//-------- 实现用户登录 --------
$users = [
  'aaa' => '123',
  'bbb' => '456',
  'ccc' => '789',
];

if (isset($_POST['user'])) {
  $user = $_POST['user'];
  $pwd = $_POST['pwd'];
  if (!isset($users[$user])) {
      echo '用户不存在';
  } else if ($users[$user] != $pwd) {
    echo '密码错误';
  } else {
    echo '登录成功,<a href="'.$_SERVER['PHP_SELF'].'">点击此处继续</a>';
    $_SESSION['user'] = $user;
    $_SESSION['logintime'] = time();
  }
} elseif (isset($_SESSION['user'])) {
  echo '当前用户:'.$_SESSION['user'].'<br>';
  if (time() - $_SESSION['logintime'] > 30) {
    echo '会话已过期,请重新登录';
    loginform();
  }
} else {
  loginform();
}

function loginform() {
  echo <<<EOF
<form action="$_SERVER[PHP_SELF]" method="post">
  用户名:<input name="user"><br>
  密码:<input name="pwd"><br>
  <input type="submit" value="提交">
</form>
EOF;
}

function my_session_start() {
  global $phpsessid, $sessfile;
 
  if (!isset($_COOKIE['PHPSESSID']) || empty($_COOKIE['PHPSESSID'])) {
    $phpsessid = my_base32_encode(my_random_bytes(16));
    setcookie('PHPSESSID', $phpsessid, ini_get('session.cookie_lifetime'), ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
  } else {
    $phpsessid = substr(preg_replace('/[^a-z0-9]/', '', $_COOKIE['PHPSESSID']), 0, 26);
  }
 
  $sessfile = ini_get('session.save_path').'/sess_'.$phpsessid;
  if (is_file($sessfile)) {
    $_SESSION = my_unserialize(file_get_contents($sessfile));
  } else {
    $_SESSION = array();
  }
  register_shutdown_function('my_session_save');
}

function my_session_save() {
  global $sessfile;
 
  file_put_contents($sessfile, my_serialize($_SESSION));
}

function my_session_id() {
  global $phpsessid;
  return $phpsessid;
}

function my_serialize($data) {
  $text = '';
  foreach ($data as $k=>$v) {
    // key cannot contains '|'
    if (strpos($k, '|') !== false) {
      continue;
    }
    $text.=$k.'|'.serialize($v)."\n";
  }
  return $text;
}

function my_unserialize($text) {
  $data = [];
  $text = explode("\n", $text);
  foreach ($text as $line) {
    $pos = strpos($line, '|');
    if ($pos === false) {
      continue;
    }
    $data[substr($line, 0, $pos)] = unserialize(substr($line, $pos + 1));
  }
  return $data;
}

function my_random_bytes($length) {
  if (function_exists('random_bytes')) {
      return random_bytes($length);
  }
  $randomString = '';
  for ($i = 0; $i < $length; $i++) { 
      $randomString .= chr(rand(0, 255));
  } 
  return $randomString;
}

function my_base32_encode($input) {
  $BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
  $output = '';
  $v = 0;
  $vbits = 0;
  for ($i = 0, $j = strlen($input); $i < $j; $i++) {
    $v <<= 8;
    $v += ord($input[$i]);
    $vbits += 8;
    while ($vbits >= 5) {
      $vbits -= 5;
      $output .= $BASE32_ALPHABET[$v >> $vbits];
      $v &= ((1 << $vbits) - 1);
    }
  }
  if ($vbits > 0) {
    $v <<= (5 - $vbits);
    $output .= $BASE32_ALPHABET[$v];
  }
  return $output;
}

回复列表(16)
  • 1
    @Ta / 06-09 20:52
    最后那个random_bytes判断是不是应该放在前面去呢。
  • 2
    @Ta / 06-09 21:08

    @石头会飞翔,对。我已经改了。

  • 3
    @Ta / 06-09 23:06
    虽然没仔细看完,不过666
  • 4
    @Ta / 06-09 23:14
    @老虎会游泳你标题红了?
  • 5
    @Ta / 06-10 01:14

    原理看了个大概,说个与主题不相关的:现在都不说用文件实现的SESSION会有性能问题吗?
    小米5黑色低配版

  • 6
    @Ta / 06-10 11:14
    @老虎会游泳,PC端Chrome浏览器,鼠标在代码快上会出现滚轮失效的问题。移动到旁边就正常了。(好像是代码块每行增加了滚动条导致的,你可以吧鼠标移动到文字上进行滚动测试)
  • 7
    @Ta / 06-10 11:21
    @水木易安,硬盘的写入速度应该不会差太多吧,而且现在的ssd泛滥的情况下磁盘io的瓶颈应该不会很容易到达。而且php支持配置化的redis存储。
  • 8
    @Ta / 06-12 03:25

    @Mattoid,嗯。我一直想换个前端代码高亮模块,不过还没有时间改。求好心人帮忙。
    @水木易安,对,所以我就不喜欢用session。直接把cookie值存入数据库多好,还可以跨服务器同步。

  • 9
    @Ta / 06-12 03:29

    @TabKey9,表情包

  • 10
    @Ta / 06-12 09:50

    @水木易安,使用数据库存取一直在我的设计印象中是能不使用数据库读取就不使用,因为我总觉得说数据库是最慢最耗费性能的一环,不知道这个观念有没有问题.
    小米5黑色低配版

  • 11
    @Ta / 06-12 09:54

    @老虎会游泳,使用数据库存取一直在我的设计印象中是能不使用数据库读取就不使用,因为我总觉得说数据库是最慢最耗费性能的一环,不知道这个观念有没有问题.
    小米5黑色低配版

  • 12
    @Ta / 06-12 18:17

    @水木易安,查询数据库比从磁盘读取大量小文件更快,特别是机械硬盘。

  • 13
    @Ta / 06-12 19:31
    Session还用file模式吗,我是直接丢给redis的,cookie读数据库会不会造成压力呢
  • 14
    @Ta / 06-12 22:07

    @老虎会游泳,是因为MySQL的数据也是存在文件系统的原因吗
    小米5黑色低配版

  • 15
    @Ta / 06-13 00:04

    @水木易安,mysql数据文件是连续的大文件,并且mysql有内存缓存。当然系统也自动对磁盘进行内存缓存,不过从文件系统读取大量小文件还是具有挑战性。
    @猫腻,如果你的每次访问实际上都要查询数据库获取内容,就无所谓cookie带来的压力了。在有索引的情况下做两个查询和做一个查询时间没什么差别。当然如果你把所有内容都缓存在redis当然更好,不过这需要很多内存啊

  • 16
    @Ta / 06-14 16:33

    来自电脑端
添加新回复
回复需要登录

[聊天-公共聊天室] 拒绝柳岩99次: