PHP_RINIT_FUNCTION(session) { php_rinit_session_globals(TSRMLS_C); if (PS(mod) == NULL) { char *value; value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0); if (value) { /* 查找save_handler */ PS(mod) = _php_find_ps_module(value TSRMLS_CC); } if (!PS(mod)) { PS(session_status) = php_session_disabled; return SUCCESS; } } if (PS(auto_start)) { /* 是否开启了session.auto_start功能 */ php_session_start(TSRMLS_C); } return SUCCESS; }Session的MINIT函数做的事情不多,主要是获取处理Session的save_handler(存储Session数据的接口)和判断是否开启了session.auto_start。
上面是Session模块被载入到PHP时所做的事情,而要在PHP脚本中使用Session功能,就必须先调用session_start()函数(在没有开启session.auto_start的情况下)。如:
<?php session_start(); /* 开启Session功能 */ $_SESSION["name"] = "Jack"; /* 设置Session数据 */ ?>
session_start()函数会调用PHP内核的php_session_start()函数来开启Session功能。php_session_start()函数做的事情主要有:1)从cookie中获取Session ID。2)根据Session ID调用save_handler的read接口来读取Session的数据。3)注册$_SESSION全局变量。php_session_start()函数代码如下:
PHPAPI void php_session_start(TSRMLS_D) { ...... if (!PS(id)) { /* 通过$_COOKIE获取Session ID */ if (PS(use_cookies) && zend_hash_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE"), (void **) &data) == SUCCESS && Z_TYPE_PP(data) == IS_ARRAY && zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS) { PPID2SID; PS(apply_trans_sid) = 0; PS(send_cookie) = 0; PS(define_sid) = 0; } } ...... php_session_initialize(TSRMLS_C); /* 注册$_SESSION全局变量 */ if (!PS(use_cookies) && PS(send_cookie)) { if (PS(use_trans_sid)) PS(apply_trans_sid) = 1; PS(send_cookie) = 0; } php_session_reset_id(TSRMLS_C); PS(session_status) = php_session_active; php_session_cache_limiter(TSRMLS_C); if (PS(mod_data) && PS(gc_probability) > 0) { int nrdels = -1; nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C)); if (nrand < PS(gc_probability)) { PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC); } } }
在上面的代码中,我们要注意php_session_initialize()这个函数,这个函数主要是调用save_handler的read接口读取Session数据和注册$_SESSION全局变量,代码如下:
static void php_session_initialize(TSRMLS_D) { ...... if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name) TSRMLS_CC) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path)); return; } /* 从客户端处读取不到Session ID时,生成新的Session ID */ if (!PS(id)) PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC); /* 注册$_SESSION和$_HTTP_SESSION_VARS全局变量 */ php_session_track_init(TSRMLS_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); } }从上面的代码可以看出,php_session_initialize()函数首先调用save_handler的open接口打开存储上下文。然后调用php_session_track_init()函数注册$_SESSION和$_HTTP_SESSION_VARS全局变量,$_SESSION和$_HTTP_SESSION_VARS会被注册为同一个数组。接着调用save_handler的read接口读取Session数据,如果是使用files方式存储的话,就从文件中读取Session数据,读取完毕后会把读到的数据写入到$_SESSION数组中。
你可能会问$_SESSION变量的值是怎么被存储的的呢?是的,前面我们只是解释了PHP怎么读取Session数据,但是它是怎么存储的呢?答案就是:当一个请求完毕的时候,PHP会调用php_session_save_current_state()函数存储$_SESSION变量的值。php_session_save_current_state()函数所做的事情是调用php_session_encode()函数把$_SESSION变量的值序列化,然后调用save_handler的write接口存储起来。代码如下:
static void php_session_save_current_state(TSRMLS_D) { int ret = FAILURE; IF_SESSION_VARS() { ...... if (PS(mod_data)) { char *val; int vallen; /* 序列化$_SESSION变量的值 */ val = php_session_encode(&vallen TSRMLS_CC); if (val) { /* 存储Session数据 */ ret = PS(mod)->s_write(&PS(mod_data), PS(id), val, vallen TSRMLS_CC); efree(val); } else { ret = PS(mod)->s_write(&PS(mod_data), PS(id), "", 0 TSRMLS_CC); } } ...... if (PS(mod_data)) PS(mod)->s_close(&PS(mod_data) TSRMLS_CC); }
至此,Session的原理基本分析完毕。不过还有个问题,就是Session ID怎么来的?因为第一次访问页面的时候不可能存在Session ID,所以PHP会在用户没有Session ID的时候调用php_session_create_id()函数自动生成一个唯一的Session ID,然后把这个Session ID通过cookie发送给用户。这个动作也是发生在调用session_start()函数的时候。