目錄
一、背景知识,session基础
二、  under the hood  - PHP中session的原理
四、参考文献
首頁 後端開發 php教程 PHP内核探索之变量(五)- session的基本原理

PHP内核探索之变量(五)- session的基本原理

Jun 13, 2016 pm 12:14 PM
nbsp php ps session

PHP内核探索之变量(5)- session的基本原理

  这次说说session.

  session可以说是当前互联网提到的最多的名词之一了。它的含义很宽泛,可以指任何一次完整的事务交互(会话):如发送一次HTTP请求并接受响应,执行一条SQL语句都可以看做一次Session。如无特殊说明,本文中提到的Session单指HTTP会话。

本文是PHP内核探索的第五篇,主要包含如下几个方面的内容:

  1. 背景知识和session基础
  2. PHP中session的原理
  3. 参考文献

一、背景知识,session基础

1.      HTTP是无状态的

  我们知道,HTTP协议最初是匿名的、无状态的请求/响应协议。这样简单的设计可以使HTTP协议专注于资源的传输(HTTP是超文本传输协议),从而获得较好的性能。但这种无状态的设计也验证阻碍了交互web应用的发展,典型的如:电商网站需要获取用户的信息,以实现订单、购物车、交易等功能,SNS网站需要获取用户信息并存档,以建立真正的“社交网络”,甚至电影和CD租赁网站,也需要获取用户信息,以提供个性化的推荐,从而带来更好的效益。这意味着,必须要使用某种技术来识别和管理用户信息,Cookie和Session技术便是在这种背景下诞生的。

2.      Session与Cookie

       说到Session,就不得不提Session的好基友Cookie,因为很多情况下Session依赖于Cookie存储其session_id。而如果要说Session和Cookie的区别,我想大家应该都不陌生,有的同学甚至可以轻松背出如下一些常见的区别:

       (1).  Cookie是客户端保持状态的解决方案,而Session是服务器端保持状态的技术,因此,Cookie是存储在客户端的,而Session是存储在服务器端的。

       (2). 大多数情况下,Session需要使用Cookie做载体,来存放session_id,所以,如果禁用了Cookie,必须要通过其他的手段来获取这个session_id( 例如通过get或者post的方式将session_id传递给服务器 )

       (3).  Cookie过期和删除只能保证客户端的连接的失效,并不会清除服务器端的Session

       (4).  尽管默认情况下,Session和Cookie都是写文件的( Session也可以写数据库或者其他内存缓存如memcached ),但是,Cookie则依赖于浏览器的设定:例如,IE6下限定每个域名下最多20个Cookie,很多浏览器限制Cookie的大小不能超过4096字节。

       关于Cookie的更多讨论,已经超出了本文的范畴,需要了解的同学可以参考《HTTP权威指南》《JavaScript高级程序设计》这两本书,相信一定会对Cookie有更加深入的理解。

3.      php中Session的基本操作

       php中,Session相关的操作是以扩展的形式提供的 ( 源码目录:PHPSRC/ext/session/ )。PHP提供了大量的、丰富的API来操作Session:

(1).   session_start

bool <span style="color: #008080;">session_start</span> ( void )
登入後複製

  session_start()用于启动一个会话,一般而言,我们在使用$_SESSION时,都要先调用session_start( 或者你的php.ini中配置了session.auto_start )。那么在session.auto_start=false的情况下, session_start是不是一定是session操作的第一个必须调用的函数呢?答案是否定的。虽然在一般情况下,我们在需要操作session时,基本上都是将session_start()放在脚本的第一行,但实际上在调用session_start时,Session相关的参数都已经初始化完毕,这之后是无法通过session_namesession_set_cookie_params, session_save_path等函数更改Session的参数信息的。所以,如果需要更改session的相关参数,除了可以在ini文件中更改(或者通过ini_set更改),还可以通过session_name, session_save_path, session_set_cookie_params等函数修改,且这些函数必须在session_start之前调用。例如:

session_save_path('/root/xiaoq/phpCode/session');session_start();$_SESSION['index'] = "this is desc";$_SESSION['int']   = 123;
登入後複製

  session_start()调用之后,除了要设置Session的基本参数之外,还会以一定的概率启动Session的GC

(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 10static 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 style="color: #008080;">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

  基于以上一些原因,在实际应用中,很多都是使用分布式内存缓存memcache或者redis来存储和共享session的。全内存操作使得session操作性能会有更大的提升。

session探索到这里就基本结束了,还有很多问题亟待解决:

  1. session的过期时间
  2. session的gc
  3. session_id的生成算法
  4. session的序列化和反序列化机制
  5. memcache, redis等对session的支持
  6. $_SESSION超全局变量的维护

这些不在一一讲解,有兴趣的同学,可以追踪一下源码实现。

由于时间仓促和个人水平有限,文中难免会有错误,欢迎指出和交流。最后,文章随意转载,但请尊重个人成果,标明出处。

四、参考文献

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

       2.    《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

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP行動:現實世界中的示例和應用程序 PHP行動:現實世界中的示例和應用程序 Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP的持久相關性:它還活著嗎? PHP的持久相關性:它還活著嗎? Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

See all articles