首先来看几个重要的数据结构,第一个ps_module_struct代表着PHP中session中要实现的几个处理session的函数指针,分别的作用是open、close、read、write、destory等功能
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变量,可以通过PS(var_name)来访问,其中部分变量代表着php.ini配置文件中的设置的参数,如save_path、session_name、use_cookie、use_http_cookie等
typedef struct _php_ps_globals {
char *save_path;
char *session_name;
char *id;
char *extern_referer_chk;
char *entropy_file;
char *cache_limiter;
long entropy_length;
long cookie_lifetime;
char *cookie_path;
char *cookie_domain;
zend_bool cookie_secure;
zend_bool cookie_httponly;
ps_module *mod;
void *mod_data;
php_session_status session_status;
long gc_probability;
long gc_divisor;
long gc_maxlifetime;
int module_number;
long cache_expire;
union {
zval *names[6];
struct {
zval *ps_open;
zval *ps_close;
zval *ps_read;
zval *ps_write;
zval *ps_destroy;
zval *ps_gc;
} name;
} mod_user_names;
zend_bool bug_compat; /* Whether to behave like PHP 4.2 and earlier */
zend_bool bug_compat_warn; /* Whether to warn about it */
const struct ps_serializer_struct *serializer;
zval *http_session_vars;
zend_bool auto_start;
zend_bool use_cookies;
zend_bool use_only_cookies;
zend_bool use_trans_sid; /* contains the INI value of whether to use trans-sid */
zend_bool apply_trans_sid; /* whether or not to enable trans-sid for the current request */
long hash_func;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
php_hash_ops *hash_ops;
#endif
long hash_bits_per_character;
int send_cookie;
int define_sid;
zend_bool invalid_session_id; /* allows the driver to report about an invalid session id and request id regeneration */
} php_ps_globals;
来看session模块启动过程,
static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */函数首先会根据php.ini文件中的save_handler配置,初始化PS全局变量中的mod,php默认的两个saveHandler可以为file和user
static ps_module *ps_modules[MAX_MODULES + 1] = { ps_files_ptr, ps_user_ptr };
我们在php代码中通过ini_set设置session.save_handler既可以通过这个函数更新PS(mod)变量
static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */ { ps_module *tmp; SESSION_CHECK_ACTIVE_STATE; tmp = _php_find_ps_module(new_value TSRMLS_CC); if (PG(modules_activated) && !tmp) { int err_type; if (stage == ZEND_INI_STAGE_RUNTIME) { err_type = E_WARNING; } else { err_type = E_ERROR; } /* Do not output error when restoring ini options. */ if (stage != ZEND_INI_STAGE_DEACTIVATE) { php_error_docref(NULL TSRMLS_CC, err_type, "Cannot find save handler '%s'", new_value); } return FAILURE; } PS(mod) = tmp; return SUCCESS; }
……看重要的session_start函数的逻辑,N长,概略的说下自己的理解,不一定对,请轻拍~
PHPAPI void php_session_start(TSRMLS_D) /* {{{ */
{
zval **ppid;
zval **data;
char *p, *value;
int nrand;
int lensess;
if (PS(use_only_cookies)) {
PS(apply_trans_sid) = 0;
} else {
PS(apply_trans_sid) = PS(use_trans_sid);
}
switch (PS(session_status)) {
case php_session_active:
php_error(E_NOTICE, "A session had already been started - ignoring session_start()");
return;
break;
case php_session_disabled:
value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
if (!PS(mod) && value) {
PS(mod) = _php_find_ps_module(value TSRMLS_CC);
if (!PS(mod)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find save handler '%s' - session startup failed", value);
return;
}
}
value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
if (!PS(serializer) && value) {
PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
if (!PS(serializer)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value);
return;
}
}
PS(session_status) = php_session_none;
/* fallthrough */
default:
case php_session_none:
PS(define_sid) = 1;
PS(send_cookie) = 1;
}
lensess = strlen(PS(session_name));
/* Cookies are preferred, because initially
* cookie and get variables will be available. */
if (!PS(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;
}
if (!PS(use_only_cookies) && !PS(id) &&
zend_hash_find(&EG(symbol_table), "_GET", sizeof("_GET"), (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(send_cookie) = 0;
}
if (!PS(use_only_cookies) && !PS(id) &&
zend_hash_find(&EG(symbol_table), "_POST", sizeof("_POST"), (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(send_cookie) = 0;
}
}
/* Check the REQUEST_URI symbol for a string of the form
* '<session-name>=<session-id>' to allow URLs of the form
* http://yoursite/<session-name>=<session-id>/script.php */
if (!PS(use_only_cookies) && !PS(id) && PG(http_globals)[TRACK_VARS_SERVER] &&
zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI"), (void **) &data) == SUCCESS &&
Z_TYPE_PP(data) == IS_STRING &&
(p = strstr(Z_STRVAL_PP(data), PS(session_name))) &&
p[lensess] == '='
) {
char *q;
p += lensess + 1;
if ((q = strpbrk(p, "/?\\"))) {
PS(id) = estrndup(p, q - p);
PS(send_cookie) = 0;
}
}
/* Check whether the current request was referred to by
* an external site which invalidates the previously found id. */
if (PS(id) &&
PS(extern_referer_chk)[0] != '\0' &&
PG(http_globals)[TRACK_VARS_SERVER] &&
zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER"), (void **) &data) == SUCCESS &&
Z_TYPE_PP(data) == IS_STRING &&
Z_STRLEN_PP(data) != 0 &&
strstr(Z_STRVAL_PP(data), PS(extern_referer_chk)) == NULL
) {
efree(PS(id));
PS(id) = NULL;
PS(send_cookie) = 1;
if (PS(use_trans_sid) && !PS(use_only_cookies)) {
PS(apply_trans_sid) = 1;
}
}
php_session_initialize(TSRMLS_C);
if (!PS(use_cookies) && PS(send_cookie)) {
if (PS(use_trans_sid) && !PS(use_only_cookies)) {
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);
#ifdef SESSION_DEBUG
if (nrdels != -1) {
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
}
#endif
}
}
}
- session_start的流程可以概括为以下几个步骤
- 首先检查session全局配置参数的值use_cookie_only和use_trans_sid,两者分别表示sessionid在客户端只能通过cookie保存和只能通过url传递
- 检查PS(session_status)状态,若是已经启动了session,则会打印一条NOTICE日志,也就是我们常见的A session had already been started
- 若是session被禁用状态,则会检查save_handler,试图初始化save_handler
- 接下来就是最重要的获取session_id的值的流程了,概略的说就是根据客户端保存cookie的方式,一次遍历$_COOKIE、$_GET、$_POST、$_SERVER这些Hashtable获取key为PS(session_name)也就是一般为PHPSESSID的值
- …未完待续