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。
save_handler是存储Sesison数据的接口,默认PHP提供了2种方式:files和user。files方式是以文件方式存储,而user方式是使用用户自定义接口来存储。
如果开启了session.auto_start,PHP便会调用php_session_start()函数自动开启Session功能。
上面是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数组中。
当php_session_initialize()函数处理完毕后,我们就可以使用$_SESSION变量了。这就是为什么在PHP脚本中调用session_start()函数会生成一个$_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()函数的时候。
来源:oschina
链接:https://my.oschina.net/u/120468/blog/78273