SESSION 用百度百科的定义为:在计算机中,尤其是在网络应用中,称为“会话”。

SESSION分为客户端session和服务器端session。

当用户首次与Web服务器建立连接的时候,服务器会给用户分发一个 SessionID作为标识。SessionID是一个由24个字符组成的随机字符串。用户每次提交页面,浏览器都会把这个SessionID包含在 HTTP头中提交给Web服务器,这样Web服务器就能区分当前请求页面的是哪一个客户端。这个SessionID就是保存在客户端的,属于客户端Session。

其实客户端Session默认是以cookie的形式来存储的,所以当用户禁用了cookie的话,服务器端就得不到SessionID。这时我们可以使用url的方式来存储客户端Session。也就是将SessionID直接写在了url中,当然这种方法不常用。
我们经常说的session其实就是服务器端session。

服务器端session的存取:

一、系统默认方式:以纯文本文件的方式存放在服务器端

服务器端session系统默认是保存在服务器上的文本中。如果服务器的访问量比较大的话,就会在服务器段生成大量的session文件。进而导致内核占用的cpu急剧上升。因为session的读写涉及到大量小文件的随机读写,并且是集中在一个目录下,iowait也急剧升高。

为了解决大量小文件的随机读写,还以将session数据文件进行分级存放。这个优化方案需要使用到php.ini的一个配置项:

;session.save_path = "N;MODE;/path"

这个选项可以让我们将session数据文件设置为多级散列存放。其中“N”就是设置目录的级数,“MODE”就是目录的权限,默认为600权限。最后的“/path”就是session文件存放的根目录。如果我们设置为:

session.save_path = "2;/tmp/phpsession"

上面的设置表示我们把/tmp/phpsession目录作为php的session文件存放根目录,在该目录下进行两级目录散列,每一级目录分别是 0-9和a-z共36个字母数字为目录名,这样存放session的目录可以达到36*36个,相信作为单台服务器来说,这是完全够用了,如果说您的系统 架构设计为多台服务器共享session数据,可以把目录级增加到3级或者更多。

需要注意的是,php自己并不会自动创建子目录,需要您自己动手去创建,网上找到这样的自动创建目录的代码,大家可以做个参考。下面的代码自动创建3级子目录,可以自己动手根据需要进行修改。

<php
set_time_limit(0);
$string = '0123456789abcdefghijklmnopqrstuvwxyz';
$length = strlen($string);
function makeDir($param)
{
     if(!file_exists($param)) 
     {
          makeDir(dirname($param));
          mkdir($param);
     }
}

for($i = 0; $i < $length; $i++) 
{
     for($j = 0; $j < $length; $j++) 
     {
          for($k = 0; $k < $length; $k++) 
          {
                makeDir($string[$i].'/'.$string[$j].'/'.$string[$k]);
          }
     }
}

二、将session信息存放到内存中

为了解决因为服务器访问流量太大导致服务器端session文件过多而导致的性能问题,可以考虑将session数据防止到内存中去。最简单的方式就是将/tmp目录挂接到tmpfs文件系统,也就是内存中。

第二步,将session存储到不通的目录中

php本身支持session的多级散列

在php.ini中,将session.save_path = /tmp;

改为 session.save_path = “2;/tmp/session”

表示将session存储到 /tmp/session这个文件夹中,并且是用2及散列。

保存退出,等第三步结束后重启php

第三步,创建session存储文件夹

php并不会自动去创建这些文件夹,不过在源文件中提供了一些创建文件夹的脚本。下面这个脚本也好用


I="0 1 2 3 4 5 6 7 8 9 a b c d e f"

for acm in $I;

do

for x in $I;

do

mkdir -p /tmp/session/$acm/$x;

done;

done

chown -R nobody:nobody /tmp/session

chmod -R 1777 /tmp/session

因为/tmp是用的内存,服务器重启后,里面的所有文件都会丢失,所以,需要把上面的脚本加入到 /etc/rc.local中,并且要放在启动php之前

第四步,session的回收

session在经过session.gc_maxlifetime后会过期,但并不会马上被删除,时间长了以后会造成/tmp空间占用很大。具体的删除算法懒得去研究。下面这个命令可以删除过期的session,我这里定义的过期时间是3小时。

find /tmp/session -amin +180 -exec rm -rf {} ;

放入cron中,10分钟执行一次,完事。

说道将session存放到内存中还有一种方式:使用memcache。

使用memcache存放session的实现方法有两种

第一种:实现一个类,使其接管默认的session的read,write等操作。代码如下:

<?php
class MemSession{
    private static $handler=null;
    private static $lifetime=null;
    private static $time= null;
    const NS='session_';

    private static  function init($handler)
    {
         self::$handler=$handler;
         self::$lifetime=ini_get('session.gc_maxlifetime');

         self::$time=time();
    }

    public static function start(Memcache $memcache)
    {
         self::init($memcache);

         session_set_save_handler(
              array(__CLASS__, "open"),
              array(__CLASS__, "close"),
              array(__CLASS__, "read"),
              array(__CLASS__, "write"),
              array(__CLASS__, "destroy"),
              array(__CLASS__, "gc")
        );
       session_start();
      }

      public static function open($path, $name){return true; }

      public static function close(){return true;}

      public static function read($PHPSESSID)
      {
           $out=self::$handler->get(self::session_key($PHPSESSID));
           if ($out===false || $out==null) {return '';}
           return $out;
      }

      public static function write($PHPSESSID,$data)
      {
            $method=$data ? 'set':'replace';
            return self::$handler->$method(self::session_key($PHPSESSID),
                                           $data,
                                           MEMCACHE_COMPRESSED,
                                           self::$lifetime);
      }

      public static function destroy($PHPSESSID) 
      {
             return self::$handler->delete(self::session_key($PHPSESSID));
      }

      public static function gc($lifetime){return true;}

      private static function session_key($PHPSESSID)
      {
             $session_key=self::NS.$PHPSESSID;
             return $session_key;
      }
}

$memcache = new Memcache;
$memcache->connect("localhost",11211) or die("could not connect memcache");
MemSession::start($memcache);

然后在需要使用session的地方 include这个文件就ok了(前提是 服务器已经正确安装的memcache以及memcache扩展)。

第二种实现方法是:

修改配置文件的2个配置项

session.save_handler = memcache
session.save_path = "tcp://127.0.0.1:11211"  #memcache的服务器地址

或者

或者某个目录下的 .htaccess :
php_value session.save_handler “memcache”
php_value session.save_path  “tcp://127.0.0.1:11211″
再或者在某个一个应用中:

ini_set("session.save_handler", "memcache");
ini_set("session.save_path", "tcp://127.0.0.1:11211″);

使 用多个 memcached server 时用逗号”,”隔开,并且和 Memcache::addServer() 文档中说明的一样,可以带额外的参数”persistent”、”weight”、”timeout”、”retry_interval” 等等,类似这样的:”tcp://host1:port1?persistent=1&weight=2,tcp://host2:port2″ 。

三、将session存放到数据库中

原理和使用memcache类接管session 的read,write一样。不同的是,将具体的session数据存放到数据库的一张表中。

代码大致如下:

首先创建表,用来保存session数据

CREATE TABLE `sessions` (
`sessionid` varchar(32) NOT NULL default '',
`data` mediumtext NOT NULL,
`lastvisit` int(10) unsigned NOT NULL default '0',
PRIMARY KEY  (`sessionid`)
) ENGINE=MyISAM DEFAULT CHARSET=gbk

主要的代码如下:

session_mysql.class.php

<?php
class session
{
      var $lifetime = 1800;
      var $op = '';

      function __construct($op = '')
      {
            $this->op = $op;
            session_set_save_handler(array($this, 'open'), 
                                     array($this, 'close'), 
                                     array($this, 'read'), 
                                     array($this, 'write'), 
                                     array($this, 'destroy'), 
                                     array($this, 'gc'));
           $this->debug('session_id:' . $_COOKIE[session_name()]);
           //确保读写session时session id保存在同一个域下,否则有可能会每次重新生成一个session id
           ini_set('session.cookie_domain', '.chf.com');
           session_start();
      }
      
      function debug($str) 
      {
            //echo $str . "<br />\n";
            $file = dirname(__FILE__) . '/session.log';
            $content = date("Y-m-d H:i:s") . ' operate:' . $this -> op . ' ' . $str . "\n";
            $fp = @fopen($file, "ab");
            if (!$fp) die("Open file $file failed!");
            @fwrite($fp, $content);
            if ($fp) @fclose($fp);
      }

      function session()
      {
           $this->__construct();
      }

    /**
     * 这个函数被session处理程序调用来作初始化工作。
     * @param string $save_path 参数$sess_path对应php.ini文件中的session.save_path选项
     * @param string $session_name 参数$session_name对应php.ini中的session.name 选项。
     */
    function open($save_path, $session_name)
    {
         $this->debug('opening...');
         global $db;
         $this->lifetime = 20; //session超时时间
          $this->time = time();
         $this->sess = &amp;$db;
         $this->pre = '';
         return true;
    }

     /*
        sess_close();
         这个函数在页面结束执行并且session处理程序需要关闭时被调用
      */
     function close()
     {
           $this->debug('closing...');
           $this->gc($this->lifetime);
           return $this->sess->close();
     } 
      
     //根据session id取出指定的session数据是否为空,然后判断用户登录与否或操作是否超时
      /* 
        这个函数在session处理程序读取指定session键值($key)时,检索并返回标识为$key的session数据.
        (注意:序列化是将变量或对象在程序结束或需要时保存在文件中,在下次程序运行或需要时再调入内存
         的技术,有别于只保存数据的方法。) 
        例如读session数据时会调用此函数
         $name = $_SESSION['name'];
        echo "name:$name<br />";

        要测试,给session赋值时也调用此函数
         $_SESSION['name'] = 'caihf';

        @param string $id session id通常通过cookie来保存用户的session id,如650fcm4p8aodn1cfrusc5ehql0
      */
      function read($id)
      {
            $this->debug('reading...');
            $sql = "SELECT data FROM `{$this->pre}sessions` WHERE sessionid='$id'";
            $r = $this->sess->get_one($sql);
            $this->debug("sql:$sql");
            return $r ? $r['data'] : '';
       } 
       /*
          这个函数据在session处理程序需要将数据保存时调用,这种情况经常在程序结束时发生。
           它负责将数据保存在下次能用sess_read($key)函数检索的地方。
           例如给session赋值时($_SESSION['name'] = 'caihf';)会调用此函数
           @param string $id session id通常通过cookie来保存用户的session id,如650fcm4p8aodn1cfrusc5ehql0
          @param string $sess_data sessoin数据,一般是自动序列化以后保存在数据库中的,如name|s:5:\"caihf\";
       */
       function write($id, $sess_data)
       {
             $this->debug('writing...');
             $sql = "REPLACE INTO `{$this->pre}sessions` (sessionid, data, lastvisit) 
                     VALUES('$id', '".addslashes($sess_data)."', '".time()."')";
             $this->sess->query($sql);
             $this->debug("sql:$sql");
             return true;
         }  
      /*
       这个函数在需要消毁session时。它负责删除session并且清除环境。
        @param string $id session id通常通过cookie来保存用户的session id,如650fcm4p8aodn1cfrusc5ehql0
       */
      function destroy($id)
      {
            $this->debug('destroying...');
            $sql = "DELETE FROM `{$this->pre}sessions` WHERE sessionid='$id'";
            $this->sess->query($sql);
            $this->debug("sql:$sql");
            return true;
      } 
      //删除超时的session数据
       /* 
        这个函数负责清理碎片。在这种情况下,它负责删除过时的session数据。session处理程序会偶尔调用它们。
       */
      function gc($maxlifetime)
      {
          $this->debug("gcing...\n");
          $expiretime = $this->time-$maxlifetime;
          $sql = "DELETE FROM `{$this->pre}sessions` WHERE lastvisit<$expiretime";
          $this->sess->query($sql);
          $this->debug("sql:$sql");
          return true;
       }
}

/*
给session赋值时的log,如$_SESSION['name'] = 'caihf';
2008-09-17 03:35:35 operate:write session_id:650fcm4p8aodn1cfrusc5ehql0
2008-09-17 03:35:35 operate:write opening...
2008-09-17 03:35:35 operate:write reading...
2008-09-17 03:35:35 operate:write sql:SELECT data FROM `sessions` WHERE sessionid='650fcm4p8aodn1cfrusc5ehql0'
2008-09-17 03:35:35 operate:write writing...
2008-09-17 03:35:35 operate:write sql:REPLACE INTO `sessions` (sessionid, data, lastvisit) VALUES('650fcm4p8aodn1cfrusc5ehql0', 'name|s:5:\"caihf\";', '1221622535')
2008-09-17 03:35:35 operate:write closing...
2008-09-17 03:35:35 operate:write gcing...
2008-09-17 03:35:35 operate:write sql:DELETE FROM `sessions` WHERE lastvisit&lt;1221622515</span>

<span style="font-size: medium;">从session中读数据时的log,如$name = $_SESSION['name'];echo "name:$name&<br />";
2008-09-17 03:44:24 operate:read session_id:650fcm4p8aodn1cfrusc5ehql0
2008-09-17 03:44:24 operate:read opening...
2008-09-17 03:44:24 operate:read reading...
2008-09-17 03:44:24 operate:read sql:SELECT data FROM `sessions` WHERE sessionid='650fcm4p8aodn1cfrusc5ehql0'
2008-09-17 03:44:24 operate:read writing...
2008-09-17 03:44:24 operate:read sql:REPLACE INTO `sessions` (sessionid, data, lastvisit) VALUES('650fcm4p8aodn1cfrusc5ehql0', 'name|s:5:\"caihf\";', '1221623064')
2008-09-17 03:44:24 operate:read closing...
2008-09-17 03:44:24 operate:read gcing...
2008-09-17 03:44:24 operate:read sql:DELETE FROM `sessions` WHERE lastvisit&lt;1221623044
*/

session_test1.php

<?php
include_once('session_mysql.class.php');
include_once('db_mysql.class.php');</span>

$db = new db_mysql();
$db->connect('localhost', 'root', 'home', 'test');
$session = new session('write');
$_SESSION['name'] = 'caihf';
//echo "name:" . $_SESSION['name'] . "<br />\n";
echo 'Assign value to session over!';

session_test2.php

<?php
include_once('session_mysql.class.php');
include_once('db_mysql.class.php');</span>

$db = new db_mysql();
$db->connect('localhost', 'root', 'home', 'test');
$session = new session('read');

function is_login()
{
     return strlen($_SESSION['name']) > 0 ? 1 : 0;
}

if (is_login()) 
{
      echo "Hello {$_SESSION['name']} <br />\n";
} 
else 
{
      echo "User not loggined! <br />\n";
}
//unset($_SESSION['name']);
/*
执行完unset($_SESSION['name']);操作,然后再刷新此页面时,字段data的值就变为空了
sessionid                  data lastvisit
am1dmhbd2dsq9fpglpdh81u0d6  1221634486
*/

参考:

http://linuxguest.blog.51cto.com/195664/424175

http://blog.csdn.net/helen_shw/article/details/6776666

http://baike.baidu.com/view/25258.htm

http://iamcaihuafeng.blog.sohu.com/100036807.html

http://blog.sina.com.cn/s/blog_495697e6010143tj.html

http://blog.sitearth.com/php%E7%9A%84session%E6%9C%BA%E5%88%B6

http://www.yibadou.com/rss/201206017361.html

http://www.cnblogs.com/wgw8299/articles/1865974.html