Heim >Backend-Entwicklung >PHP-Tutorial >Im PHP-Kernel untersuchte Variablen (5) – Grundprinzipien der Sitzung

Im PHP-Kernel untersuchte Variablen (5) – Grundprinzipien der Sitzung

WBOY
WBOYOriginal
2016-08-08 09:30:29973Durchsuche

Lass uns dieses Mal über die Sitzung sprechen.

Sitzung gilt als einer der am häufigsten genannten Begriffe im Internet. Seine Bedeutung ist sehr weit gefasst und kann sich auf jede vollständige Transaktionsinteraktion (Sitzung) beziehen: beispielsweise das Senden einer HTTP-Anfrage und das Empfangen einer Antwort oder das Ausführen einer SQL-Anweisung, die als Sitzung betrachtet werden kann. Sofern nicht anders angegeben, bezieht sich die in diesem Artikel erwähnte Sitzung nur auf die HTTP-Sitzung.

Dieser Artikel ist der fünfte Artikel zur PHP-Kernel-Erkundung. Er umfasst hauptsächlich die folgenden Aspekte:

  1. Hintergrundwissen und Sitzungsgrundlagen
  2. Das Sitzungsprinzip in PHP
  3. Referenzen

1. Hintergrundwissen, Sitzungsgrundlagen

1. HTTP ist zustandslos

Wir wissen, dass das HTTP-Protokoll ursprünglich ein anonymes, zustandsloses Anfrage-/Antwortprotokoll war. Ein derart einfaches Design ermöglicht es dem HTTP-Protokoll, sich auf die Übertragung von Ressourcen zu konzentrieren (HTTP ist das Hypertext Transfer Protocol) und dadurch eine bessere Leistung zu erzielen. Dieses zustandslose Design behindert jedoch auch nachweislich die Entwicklung interaktiver Webanwendungen. Typische Beispiele sind: E-Commerce-Websites müssen Benutzerinformationen abrufen, um Funktionen wie Bestellungen, Einkaufswagen und Transaktionen zu implementieren, und SNS-Websites müssen Benutzerinformationen erhalten Um ein echtes „soziales Netzwerk“ aufzubauen, müssen auch Film- und CD-Verleih-Websites Benutzerinformationen einholen, um personalisierte Empfehlungen zu geben und so einen besseren Nutzen zu erzielen. Das bedeutet, dass eine Art Technologie zur Identifizierung und Verwaltung von Benutzerinformationen verwendet werden muss. In diesem Zusammenhang wurden Cookies und Sitzungstechnologien entwickelt.

2. Sitzung und Cookie

Apropos Session: Wir müssen den guten Freund von Session, Cookie, erwähnen, da Session in vielen Fällen auf Cookie angewiesen ist, um seine session_id zu speichern. Wenn wir über den Unterschied zwischen Session und Cookie sprechen wollen, sollte meiner Meinung nach jeder damit vertraut sein. Einige Schüler können sogar die folgenden allgemeinen Unterschiede leicht aufzählen:

(1). Cookie ist eine Lösung zur Aufrechterhaltung des Status auf der Clientseite, während Session eine Technologie zur Aufrechterhaltung des Status auf der Serverseite ist. Daher wird Cookie auf der Clientseite gespeichert, während Session auf der Serverseite gespeichert wird der Serverseite.

(2) In den meisten Fällen muss Session ein Cookie als Träger zum Speichern der session_id verwenden. Wenn Cookie deaktiviert ist, muss die session_id daher auf andere Weise abgerufen werden (z. B. über get oder post). Methode zur Übergabe der session_id an den Server)

(3). Das Ablaufen und Löschen von Cookies kann nur die Ungültigkeit der Clientverbindung sicherstellen und löscht nicht die serverseitige Sitzung

(4) Obwohl standardmäßig sowohl Session- als auch Cookie-Dateien geschrieben werden (Session kann auch in die Datenbank oder einen anderen Speicher-Cache wie Memcached schreiben), hängt Cookie jedoch von den Browsereinstellungen ab: IE6 begrenzt beispielsweise a Maximal 20 Cookies pro Domainname, und viele Browser begrenzen die Größe von Cookies auf nicht mehr als 4096 Byte.

Weitere Diskussionen über Cookies würden den Rahmen dieses Artikels sprengen. Schüler, die mehr wissen müssen, können sich auf „HTTP Authoritative Guide“ und „JavaScript Advanced Programming“ beziehen Ich glaube, dass Sie mit diesen beiden Büchern ein tieferes Verständnis für Cookies erlangen werden.

3. Grundlegende Operationen der Sitzung in PHP

PHP, der Sitzungsbetrieb wird in Form einer Erweiterung bereitgestellt (Quellverzeichnis: PHPSRC/EXT/Session/). PHP bietet eine große Anzahl umfangreicher APIs zum Betrieb von Session:

(1). session_start

bool <span>session_start</span> ( void )

session_start() wird verwendet, um eine Sitzung zu starten. Wenn wir $_SESSION verwenden, müssen wir im Allgemeinen zuerst session_start aufrufen (oder session.auto_start ist in Ihrer php.ini konfiguriert). Ist session_start im Fall von session.auto_start=false unbedingt die erste Funktion, die für den Sitzungsbetrieb aufgerufen werden muss? Die Antwort ist nein. Wenn wir jedoch unter normalen Umständen eine Sitzung ausführen müssen, setzen wir session_start() in die erste Zeile des Skripts. Tatsächlich wurden beim Aufruf von session_start alle mit der Sitzung verbundenen Parameter initialisiert Sitzungsparameterinformationen können nicht durch Funktionen wie session_name und session_set_cookie_params, session_save_path geändert werden. Wenn Sie daher die relevanten Parameter der Sitzung ändern müssen, können Sie sie zusätzlich zur Änderung in der INI-Datei (oder über ini_set) auch über Funktionen wie Sitzungsname, Sitzungsspeicherpfad, Sitzungssatz_cookie_params ändern, und diese Funktionen müssen muss vor session_start aufgerufen werden. Zum Beispiel:

session_save_path('/root/xiaoq/phpCode/session');
session_start();

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;

Nach dem Aufruf von session_start() wird zusätzlich zum Festlegen der Grundparameter der Sitzung mit einer bestimmten Wahrscheinlichkeit auch der GC der Sitzung gestartet.

(2). session_id()

  如同数据库中每条记录需要一个主键一样,Session也需要一个id值用于唯一标识一个Client,这个标识便是session_id。函数session_id()用于设置或者更改当前会话的session_id,例如:

session_save_path('/root/xiaoq/phpCode/session');
session_start();                                   

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;

print_r( session_id());//5rdhbe4k8k73h5g1fsii01iau5

在设置了session.save_handler=files的情况下,服务器端是以sess_{session_id}的命名方式来储存Session数据文件的:

 

  正常情况下,不同会话的session_id是不会重复的。在已知session_id的情况下,我们可以通过传递session_id的方法来获取Session数据,从而避开Cookie的限制:

session_save_path('/root/xiaoq/phpCode/session');
session_id("5rdhbe4k8k73h5g1fsii01iau5");
session_start();

print_r($_SESSION);
/* Array
(
    [index] => this is desc
    [int] => 123
) */

  Session文件存储会有很多问题和瓶颈,关于这一点,之后也会有详细的说明和解释。

(4).  session_write_close/session_commit

  默认情况下,session数据是在当前会话结束时(一般就是指脚本执行完毕时)才会写入文件的,这样会带来一些问题。例如,如果当前脚本执行过长,那么当其他脚本访问同一session_id下的session数据时便会阻塞(这实际上会涉及到文件锁flock,之后会有说明),直到前一脚本执行完毕并写入session文件。可以用sleep来简单模拟这一情况:

session_save_path('/root/xiaoq/phpCode/session');
session_start();

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;

sleep(15);

  避免这一情况的一种方法是:在session数据使用完毕之后,调用session_commit或者session_write_close函数通知服务器写入session数据并关闭文件(释放flock的锁):

session_save_path('/root/xiaoq/phpCode/session');
session_start();

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;
session_commit();

sleep(15);

注意session_commit和session_write_close只是同一函数的不同别名。

(5).  session_destroy

       很多同学在会话结束的时候,都是通过unset($_SESSION)的方式来删除会话数据(这与session_unset()的作用类似)。实际上这样并不是稳妥的做法,原因是:unset($_SESSION)只是重置$_SESSION这个全局变量,并不会将session数据从服务器端删除。较为稳妥的做法是,在需要清除当前会话数据的时候调用session_destroy删除服务器端Session数据(同时,最好使Cookie也过期):

session_save_path('/root/xiaoq/phpCode/session');
session_start();

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;

unset($_SESSION);
session_destroy();

3.  session的ini配置

       由于Session的很多操作依赖于ini中的参数配置,因此我们有必要对此做一个比较全面的了解。php.ini比较重要的Session参数配置包括:

       (1).  session.save_handler

       这个参数用于指定Session的存储方式(实际上是指定了一个处理Session的句柄)。可以是files(文件存储,默认), user( 用户自定义存储 ),或者其他的扩展方式(如memcache)。

       (2).  session.save_path

       在使用session.save_handler=files的情况下,session.save_path用于指定Session文件存储的目录,如session.save_path= “/tmp”;这种配置下,所有的session文件都是写入一个目录的。这在某些情况下是有问题的(如有的系统单目录下支持的文件数是有限制的,而且,同一目录下文件过多,会造成读取变慢)。session.save_path还支持多级目录hash的方式:session.save_path = "N;/path"; 这种配置方式会将session文件分散到不同的子目录中,避免单目录文件文件过多。同样,这种配置方式也有较大的问题:如Session的GC是无效的,而且,PHP并不会自动为你创建子目录,需要手动创建或者通过脚本创建。

       (3).  session.name

       在使用Cookie为载体的情况下,session.name指定存储session_id的Cookie的key( cookie中也是基于key=>value)。默认的情况下,session.name= PHPSESSID

,可以更改为任何合法的其他名称。同样,也可以通过session_name函数,在调用session_start之前设置这个key的名称:

session_name("NEW_SESSION");
session_start();

$_SESSION['index'] = "this is desc";
$_SESSION['int']   = 123;

抓包可以看到,现在,Cookie中是以新的session.name来传递session_id了,而第一次服务器端的响应中,也会发送Set-Cookie:

 

       (4).  session.auto_start

       这个参数用于指定是否需要自动开启session,在设置为true的情况下,不需要在脚本中显式的调用session_start(). 如果不是特殊需要,我们并不建议开启session.auto_start.

       (5).  session.gc_*

       主要用于配置session GC的相关参数。关于这点,我们在后面会有详细讲解,这里暂时搁置

       (6).  session.cookie_*

       主要用于配置session的载体cookie的相关参数信息,如cookie的path, lifetime, 域domain等。

       关于Session的更多配置,可以参考:

http://cn2.php.net/manual/zh/session.configuration.php

二、  under the hood  - PHP中session的原理

       现在,我们对Session已经有了一个基本的认识,接下来,我们将更深入的去探讨和挖掘Session的更多细节。这一部分的内容比较枯燥乏味,对于不需要了解Session内部细节的同学,完全可以略过。接下来的部分,如果没有特殊说明,都是指session.save_handler=files的情况。

1.      session模块的初始化MINIT

       前面我们提到,在php中,Session是以扩展的形式加载的,因此,它也会经历扩展的MINIT -> RINIT -> RSHUTDOWN -> MSHUTDOWN等阶段。PHP_MINIT_FUNCTION和PHP_RINIT_FUNCTION是php启动过程中两个关键点:在php启动时,会依次调用各个扩展模块的PHP_MINIT_FUNCTION来完成各个扩展模块的初始化工作,而PHP_RINIT_FUNCTION则在对模块的请求到来时作一些准备性工作。对于Session而言,PHP_MINIT_FUNCTION主要完成的初始化工作包括(注:不同版本的PHP具体处理过程并不完全相同,如PHP 5.4+提供了SessionHandlerInterface,这样可以通过session_set_save_handler ( SessionHandlerInterface $sessionhandler )的方式自定义Session的处理机制,而不必像之前一样使用冗长的bool session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid ] )):

(1).  注册$_SESSION超全局变量:

zend_register_auto_global("_SESSION", sizeof("_SESSION")-1, NULL TSRMLS_CC);

也就是说,$_SESSION超全局变量实际上是在session的MINIT阶段被注册的。

(2).  读取ini文件中的相关配置。

REGISTER_INI_ENTRIES();

 REGISTER_INI_ENTRIES();实际上是一个宏定义:

#define REGISTER_INI_ENTRIES() zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)

因此,实际上是调用zend_register_ini_entries(ini_entries, module_number TSRMLS_CC)。关于ini文件的解析和配置,已经超出了本文的范畴,可以参考这篇文章:http://www.cnblogs.com/driftcloudy/p/4011954.html 。

   扩展中读取和设置ini的相关配置位于PHP_INI_BEGIN和PHP_INI_END宏之间。对于session而言,实际上包括:

PHP_INI_BEGIN()

       STD_PHP_INI_BOOLEAN("session.bug_compat_42",    "1",         PHP_INI_ALL, OnUpdateBool,   bug_compat,         php_ps_globals,    ps_globals)
       STD_PHP_INI_BOOLEAN("session.bug_compat_warn",  "1",         PHP_INI_ALL, OnUpdateBool,   bug_compat_warn,    php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.save_path",          "",          PHP_INI_ALL, OnUpdateSaveDir,save_path,          php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.name",               "PHPSESSID", PHP_INI_ALL, OnUpdateString, session_name,       php_ps_globals,    ps_globals)
       PHP_INI_ENTRY("session.save_handler",           "files",     PHP_INI_ALL, OnUpdateSaveHandler)
       STD_PHP_INI_BOOLEAN("session.auto_start",       "0",         PHP_INI_ALL, OnUpdateBool,   auto_start,         php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.gc_probability",     "1",         PHP_INI_ALL, OnUpdateLong,   gc_probability,     php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.gc_divisor",         "100",       PHP_INI_ALL, OnUpdateLong,   gc_divisor,         php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.gc_maxlifetime",     "1440",      PHP_INI_ALL, OnUpdateLong,   gc_maxlifetime,     php_ps_globals,    ps_globals)
       PHP_INI_ENTRY("session.serialize_handler",      "php",       PHP_INI_ALL, OnUpdateSerializer)
       STD_PHP_INI_ENTRY("session.cookie_lifetime",    "0",         PHP_INI_ALL, OnUpdateLong,   cookie_lifetime,    php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.cookie_path",        "/",         PHP_INI_ALL, OnUpdateString, cookie_path,        php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.cookie_domain",      "",          PHP_INI_ALL, OnUpdateString, cookie_domain,      php_ps_globals,    ps_globals)
       STD_PHP_INI_BOOLEAN("session.cookie_secure",    "",          PHP_INI_ALL, OnUpdateBool,   cookie_secure,      php_ps_globals,    ps_globals)
       STD_PHP_INI_BOOLEAN("session.cookie_httponly",  "",          PHP_INI_ALL, OnUpdateBool,   cookie_httponly,    php_ps_globals,    ps_globals)
       STD_PHP_INI_BOOLEAN("session.use_cookies",      "1",         PHP_INI_ALL, OnUpdateBool,   use_cookies,        php_ps_globals,    ps_globals)
       STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1",         PHP_INI_ALL, OnUpdateBool,   use_only_cookies,   php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.referer_check",      "",          PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.entropy_file",       "",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.entropy_length",     "0",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.cache_limiter",      "nocache",   PHP_INI_ALL, OnUpdateString, cache_limiter,      php_ps_globals,    ps_globals)
       STD_PHP_INI_ENTRY("session.cache_expire",       "180",       PHP_INI_ALL, OnUpdateLong,   cache_expire,       php_ps_globals,    ps_globals)
       PHP_INI_ENTRY("session.use_trans_sid",          "0",         PHP_INI_ALL, OnUpdateTransSid)
       PHP_INI_ENTRY("session.hash_function",          "0",         PHP_INI_ALL, OnUpdateHashFunc)
       STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4",    PHP_INI_ALL, OnUpdateLong,   hash_bits_per_character, php_ps_globals, ps_globals)
PHP_INI_END()

       如果在ini文件中没有配置相关的参数项,在session的MINIT阶段,参数会被初始化为默认的值。

(3).  自php 5.4起,php提供了SessionHandlerSessionHandlerInterface这两个Class, 因此还需要对这两个Class做相关的初始化工作。这是通过:

INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions);

INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions);

来实现的,有兴趣的同学可以查看具体的实现过程,这里不再赘述。

2.      session请求时的准备RINIT

PHP_RINIT_FUNCTION(session) 用于完成session请求之时的准备工作,主要包括:

(1).  初始化session相关的全局变量,这是通过php_rinit_session_globals来完成的:

static inline void php_rinit_session_globals(TSRMLS_D)
{
    PS(id) = NULL;//session的id
    PS(session_status) = php_session_none;//初始化session_status
    PS(mod_data) = NULL;//session data
    PS(mod_user_is_open) = 0;
    /* Do NOT init PS(mod_user_names) here! */
    PS(http_session_vars) = NULL;
}

(2). 根据ini的配置查找session.save_handler,从而确定是使用files还是user( 或者是其他的扩展方式)来处理session:

if (PS(mod) == NULL) {
    char *value;

    value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
    if (value) {
        PS(mod) = _php_find_ps_module(value TSRMLS_CC);
    }
}

  确定是user还是files来处理session的逻辑是由_php_find_ps_module来完成的,这个函数会依次查找ps_modules中预定义的module, 一旦查找成功,立即返回:

PHPAPI ps_module *_php_find_ps_module(char *name TSRMLS_DC)
{
       ps_module *ret = NULL;
       ps_module **mod;
       int i;
      
      for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
              if (*mod && !strcasecmp(name, (*mod)->s_name)) {
                     ret = *mod;
                     break;
              }
       }
       return ret;
}

ps_modules的定义:

#define MAX_MODULES 10

static ps_module *ps_modules[MAX_MODULES + 1] = {
    ps_files_ptr,// &ps_mod_files
    ps_user_ptr//&ps_mod_user
};

而每一个ps_module,实际上是一个struct:

typedef struct ps_module_struct {
    const char *s_name;
    int (*s_open)(PS_OPEN_ARGS);
    int (*s_close)(PS_CLOSE_ARGS);
    int (*s_read)(PS_READ_ARGS);
    int (*s_write)(PS_WRITE_ARGS);
    int (*s_destroy)(PS_DESTROY_ARGS);
    int (*s_gc)(PS_GC_ARGS);
    char *(*s_create_sid)(PS_CREATE_SID_ARGS);
} ps_module;

  这意味着,每一个处理session的mod,不管是files, user还是其他扩展的模块,都应该包含ps_module中定义的字段,分别是:module的名称(s_name), 打开句柄函数(s_open), 关闭句柄函数(s_close), 读取函数(s_read) , 写入函数(s_write), 销毁函数(s_destroy), gc函数(s_gc),生成session_id的函数(s_create_sid)。例如,对于session.save_handler=files而言,实际上是:

{
       "files",
       ps_open_files,
       ps_close_files,
       ps_read_files,
       ps_write_files,
       ps_delete_files,
       ps_gc_files,
       php_session_create_id
}

  很多模块都是以PS_MOD(module_name)的方式定义,上述files的ps_module结构,便是PS_MOD(files)宏展开后的结果:

#define PS_MOD(x) \
    #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \
     ps_delete_##x, ps_gc_##x, php_session_create_id

       上述宏定义我们也可以看出,session.save_handler不管是files, user,还是其他的session处理的handler(如memcache, redis等) 生成session_id的算法都是使用php_session_create_id函数来实现的。

       我们花费了大量的精力来说session.save_handler, 其实是想说明:原则上,session可以存储在任何可行的存储中的(例如文件,数据库,memcache和redis),如果你自己开发了一个存储系统,比memcache的性能更好,那么OK, 你只要按照session存储的规范,设置好session.save_handler,不管是你在脚本中提供接口还是使用扩展,可以很方便的操作session数据,是不是很方便?

       接着说RINIT的过程。

       确定完session的save_handler之后。需要确定serializer, 这个也是必须的。Serializer用于完成session数据的序列化和反序列化,我们在session.save_handler=files的情况下可以看到,session数据并不是直接写入文件的,而是通过一定的序列化机制序列化之后存储到文件的,在读取session数据时需要对文件的内容进行反序列化:

session_save_path('/root/xiaoq/phpCode/session');
session_start();

$_SESSION['key'] = 'value';
session_write_close();

则相应session文件的内容是:

<span>key</span>|s:5:"value"

查找serializer的过程与查找PS(mod)的方式类似:

if (PS(serializer) == NULL) {
    char *value;

    value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);

    if (value) {
        PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
    }
}

_php_find_ps_serializer也是在预定义的ps_serializers数组中查找:

PHPAPI const ps_serializer *_php_find_ps_serializer(char *name TSRMLS_DC) {
    const ps_serializer *ret = NULL;
    const ps_serializer *mod;

    for (mod = ps_serializers; mod->name; mod++) {
        if (!strcasecmp(name, mod->name)) {
            ret = mod;
            break;
        }
    }
    return ret;
}

static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
    PS_SERIALIZER_ENTRY(php_serialize),
    PS_SERIALIZER_ENTRY(php),
    PS_SERIALIZER_ENTRY(php_binary)
};

同样,每一个serializer都是一个struct:

typedef struct ps_serializer_struct {
    const char *name;
    int (*encode)(PS_SERIALIZER_ENCODE_ARGS);
    int (*decode)(PS_SERIALIZER_DECODE_ARGS);
} ps_serializer;

       这时,如果mod不存在(设置的session.save_handler错误)或者serializer不存在,那么直接标记session_status为php_session_disabled,并返回,后面的代码不再执行。否则,确定了mod和serializer,如果设置了session.auto_start,那么就自动开启session:

if (auto_start) {
    php_session_start(TSRMLS_C);
}

由于session_start()时,也是调用php_session_start开启session,因此我们捎带着把session_start也一并分析。

3.      session_start

   session_start用于开启或者重用现有的会话,在底层,其实现为:

static PHP_FUNCTION(session_start)
{
    php_session_start(TSRMLS_C);

    if (PS(session_status) != php_session_active) {
        RETURN_FALSE;
    }
    RETURN_TRUE;
}

  内部是调用php_session_start完成session相关上下文的设置, 其基本步骤是:

(1).  检查当前会话的session状态。

php_session_status用于标志所有可能的会话状态,它是一个enum:

typedef enum {      
    php_session_disabled,
    php_session_none,
    php_session_active
} php_session_status;

那么可能的情况有:

  (a). session_status = php_session_active

  表明已经开启了session。那么忽略本次的session_start(), 但同时会产生一条警告信息:

A session had already been started - ignoring session_start()

  (b). session_status = php_session_ disabled

这种情况可能发生在RINIT的过程中,前面我们看到:

if (PS(mod) == NULL || PS(serializer) == NULL) {
    /* current status is unusable */

    PS(session_status) = php_session_disabled;
    return SUCCESS;
}

如果session_status = php_session_ disabled, 无法确定session是否真不可用(比如我们在脚本中设置了session_set_save_handler),还要做进一步的分析。查找mod和serializer的过程与RINIT的类似。

  (c). session_status = php_session_none

  在session_status= php_session_ disabled和php_session_none的情况下,都会继续向下执行。

(2).  如果session_id不存在,那么内核会依次尝试下列方法获取session_id(为了方便起见,我们直接使用了$_COOKIE, $_GET, $_POST,实际上这样是不严谨的,因为这些超级全局变量是php内核生成并提供给应用程序的,内核实际上是在全局的symbol_table中查找)

a.    $_COOKIE中

b.    $_GET中

c.    $_POST中

任何一此查找成功都会设置PS(id),不再继续查找。

(3).  执行php_session_initialize完成session的初始化工作。  

       注意此时PS(id)依然可能是NULL,这通常发生在第一次访问页面的时候。php_session_initialize完成的主要工作包括:

  a.  安全性检查

  正常情况下,生成的session_id不会包含html标签,单双引号和空白字符的,如果session_id中包含了这些非法的字符,那么很有可能session_id是伪造的。对于这种情况,处理很简单,释放session_id的空间,并标志为NULL,这样与第一次访问页面时的逻辑就基本一致了:

if (PS(id) && strpbrk(PS(id), "\r\n\t <>'\"\\")) {
    efree(PS(id));
    PS(id) = NULL;
}

  b.  为了稳妥起见,这里再次验证PS(mod)是否存在,如果不存在则返回错误。

  在PS(mod)存在的情况下,尝试打开句柄(对于session.save_handler=files而言,实际上是打开文件)。

  c.  session_id

  如果session_id不存在,那么会调用相应模块的s_create_sid方法创建相应的session_id。实际上,不管是user, files还是memcache,创建session_id时都是调用的PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS);有兴趣的同学可以看看生成session_id的算法,比较复杂,由于篇幅问题,这里并不跟踪。

  d.  尝试读取数据

  如果读取失败,则可能原因是session_id是无效的,那么重新尝试c中的步骤,直到读取成功。

if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == SUCCESS) {
    php_session_decode(val, vallen TSRMLS_CC);
    efree(val);
} else if (PS(invalid_session_id)) { /* address instances where the session read fails due to an invalid id */
    PS(invalid_session_id) = 0;
    efree(PS(id));
    PS(id) = NULL;
    goto new_session;
}

在这之前,其实还有一个逻辑:php_session_track_init,用于清除PHP中已经存在的$_SESSION数组(可能是垃圾数据):

static void php_session_track_init(TSRMLS_D)
{
    zval *session_vars = NULL;

    /* Unconditionally destroy existing array -- possible dirty data */
    zend_delete_global_variable("_SESSION", sizeof("_SESSION")-1 TSRMLS_CC);

    if (PS(http_session_vars)) {
        zval_ptr_dtor(&PS(http_session_vars));
    }

    MAKE_STD_ZVAL(session_vars);
    array_init(session_vars);
    PS(http_session_vars) = session_vars;

    ZEND_SET_GLOBAL_VAR_WITH_LENGTH("_SESSION", sizeof("_SESSION"), PS(http_session_vars), 2, 1);
}

4.      session的基本流程

到这里,session_start的流程基本走完了。我们据此总结一下在session.save_handler=files情况下,session的基本流程:

  • php启动的时候,完成session模块的初始化,其中包含对ini中session参数的处理。
  • 用户请求到达,完成模块的RINIT。如果ini中配置了session.auto_start,或者用户调用session_start,便开启session。
  • 尝试从Cookie, Get, Post中获取session_id, 如果没有获取到,说明这是一个新的session,则调用相应的算法生成session_id。打开对应的session文件。
  • 用户的业务逻辑,大多数情况下会包含对$_SESSION全局变量的操作。这些session数据并不是直接写入文件,而是存在内存中。
  • 调用session_commit或者脚本执行完毕时,session数据写入文件,关闭打开的session文件句柄。如果session_id是以Cookie存储的,那么在服务器端的响应中,还应该发送Set-Cookie的HTTP头,通知客户端存储session_id,之后的每次请求都应该携带这个session_id.

5.  session文件存储的问题

让我们回到之前提出的问题:在session.save_handler=files的情况下,会有哪些性能问题和瓶颈?

  a.  文件锁带来的性能问题

  前面我们已经提到,如果一个脚本的处理时间过程,且其中包含session的相关操作,那么其他脚本在访问session数据时便会阻塞,直到前一脚本执行完毕,这是为什么呢?在session/mod_files.c中ps_files_open函数中追踪到这样一句:

flock(data->fd, LOCK_EX);

由于是LOCK_EX(互斥锁),因而在文件锁定期间,即使是读取文件的数据也是不允许的。这就造成要写入或读取的进程必须等待,直到前一进程释放锁(这通常发生在脚本执行完毕或者用户调用session_commit/session_write_close)。

  b.  分布式服务器环境下session共享的问题

session文件存储实际上是存储在服务器的磁盘上的,这样在分布式服务器环境下会造成一定的问题:假如你有a,b,c三台服务器。则用户的多次请求可能按照负载均衡策略定向到不同的服务器,由于服务器之间并没有共享session文件,这在表象看来便发生了session丢失。这虽然可以通过用户粘滞会话解决,但会带来更大的问题:无法服务器的负载均衡,增加了服务器的复杂性

  c.  高并发场景下session,大量磁盘I/O

Aus den oben genannten Gründen verwenden viele in praktischen Anwendungen den verteilten Speicher-Cache Memcache oder Redis zum Speichern und Freigeben von Sitzungen. Der Betrieb mit vollem Speicher verbessert die Leistung des Sitzungsbetriebs erheblich.

Die Sitzungserkundung ist im Grunde hier vorbei und es gibt noch viele Probleme, die gelöst werden müssen:

  1. Ablaufzeit der Sitzung
  2. Sitzungs-GC
  3. Algorithmus zur Sitzungs-ID-Generierung
  4. Sitzungsserialisierungs- und Deserialisierungsmechanismus
  5. Memcache, Redis usw. Support-Sitzung
  6. Wartung der superglobalen Variablen $_SESSION

Diese werden nicht einzeln erklärt. Interessierte Schüler können die Implementierung des Quellcodes verfolgen.

Aufgrund des Zeitdrucks und der begrenzten persönlichen Ebene wird es zwangsläufig zu Fehlern im Artikel kommen, die gerne darauf hingewiesen und kommuniziert werden. Abschließend können Sie den Artikel gerne noch einmal abdrucken, aber bitte respektieren Sie Ihre persönlichen Leistungen und geben Sie die Quelle an.

4. Referenzen

1. http://www.tuicool.com/articles/26Rrui

2. „Der endgültige Leitfaden zu HTTP“

3. http://www.cnblogs.com/shiyangxt/archive/2008/10/07/1305506.html

4. http://blog.163.com/lgh_2002/blog/static/4401752620105246517509/

5. http://www.cnblogs.com/driftcloudy/p/4011954.html

Das Obige hat die Grundprinzipien der Variablen (5)-Sitzung in der PHP-Kernel-Erkundung vorgestellt, einschließlich der relevanten Aspekte. Ich hoffe, dass es für Freunde hilfreich sein wird, die sich für PHP-Tutorials interessieren.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn