亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > PHP > 正文

PHP內核探索之變量(5) session的基本理

2020-03-22 17:26:06
字體:
來源:轉載
供稿:網友
  •   這次說說session.

      session可以說是當前互聯網提到的最多的名詞之一了。它的含義很寬泛,可以指任何一次完整的事務交互(會話):如發送一次HTTP請求并接受響應,執行一條SQL語句都可以看做一次Session。如無特殊說明,本文中提到的Session單指HTTP會話。

    本文是PHP內核探索的第五篇,主要包含如下幾個方面的內容:

    背景知識和session基礎 PHP中session的原理 參考文獻一、背景知識,session基礎

    1. HTTP是無狀態的

      我們知道,HTTP協議最初是匿名的、無狀態的請求/響應協議。這樣簡單的設計可以使HTTP協議專注于資源的傳輸(HTTP是超文本傳輸協議),從而獲得較好的性能。但這種無狀態的設計也驗證阻礙了交互web應用的發展,典型的如:電商網站需要獲取用戶的信息,以實現訂單、購物車、交易等功能,SNS網站需要獲取用戶信息并存檔,以建立真正的“社交網絡”,甚至電影和CD租賃網站,也需要獲取用戶信息,以提供個性化的推薦,從而帶來更好的效益。這意味著,必須要使用某種技術來識別和管理用戶信息,html' target='_blank'>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 session_start ( 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_name和session_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的句柄)??梢允莊iles(文件存儲,默認), 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提供了SessionHandler和SessionHandlerInterface這兩個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文件的內容是:

    key|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), '	 <>''/')) {    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探索到這里就基本結束了,還有很多問題亟待解決:

    session的過期時間 session的gc session_id的生成算法 session的序列化和反序列化機制 memcache, redis等對session的支持 $_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

    PHP編程

    鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

  • 發表評論 共有條評論
    用戶名: 密碼:
    驗證碼: 匿名發表
    亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
    国产精品视频久久久久| 51ⅴ精品国产91久久久久久| 91国产高清在线| 欧美一区二区.| 性色av一区二区三区| 81精品国产乱码久久久久久| 欧美电影免费观看高清| 亚洲自拍偷拍在线| 日韩精品在线影院| 欧美老少做受xxxx高潮| 在线视频亚洲欧美| 精品偷拍各种wc美女嘘嘘| 亚洲欧美一区二区三区情侣bbw| 一道本无吗dⅴd在线播放一区| 国产精品人成电影在线观看| 欧洲成人性视频| 亚洲第一区中文字幕| 精品久久久久人成| 亚洲色图第一页| 国产美女91呻吟求| 国产精品久久97| 亚洲男人天堂网| 国产午夜精品美女视频明星a级| 久久成人综合视频| 精品久久久久久久久久| 亚洲欧美综合v| 欧美在线观看网站| 法国裸体一区二区| 日韩av有码在线| 欧美高清视频在线播放| 日韩欧美一区视频| 日韩欧美在线免费观看| 欧美黑人xxxx| 国产亚洲欧洲高清| 中文字幕自拍vr一区二区三区| 亚洲精品国产拍免费91在线| 久久全国免费视频| 亚洲最大的免费| 久久精品青青大伊人av| 久久久日本电影| 欧美一级电影久久| 狠狠久久五月精品中文字幕| 欧美国产视频一区二区| 国产91成人在在线播放| 亚洲自拍欧美色图| 国产精品高清在线| 国产精品影院在线观看| www.亚洲一区| 欧美裸体男粗大视频在线观看| 在线观看欧美成人| 国产精品视频一区二区高潮| 国产一区二区三区久久精品| 国产精品久久久久91| 91视频8mav| 亚洲第一色中文字幕| 2021久久精品国产99国产精品| 中文字幕精品在线视频| 91精品国产91久久| 91精品视频免费| 亚洲免费av网址| 97国产suv精品一区二区62| 亚洲的天堂在线中文字幕| 美女视频黄免费的亚洲男人天堂| 亚洲欧美精品伊人久久| 国语自产在线不卡| 国产精自产拍久久久久久蜜| 自拍视频国产精品| 成人黄色大片在线免费观看| 久久国产精品99国产精| 成人97在线观看视频| 日韩av免费观影| 成人伊人精品色xxxx视频| 在线日韩日本国产亚洲| 国内精品美女av在线播放| 最新国产成人av网站网址麻豆| 日韩av在线高清| 国产噜噜噜噜噜久久久久久久久| 欧美日韩爱爱视频| 亚洲精品自拍第一页| 久久av红桃一区二区小说| 亚洲精品91美女久久久久久久| 91精品免费视频| 亚洲女性裸体视频| 一区二区三欧美| 国内精品一区二区三区四区| 国产精品天天狠天天看| 久久视频免费在线播放| 91亚洲精品在线观看| 国产精品精品一区二区三区午夜版| 亚洲国产日韩欧美在线99| 97视频在线观看免费高清完整版在线观看| 日韩电影中文字幕av| 2018中文字幕一区二区三区| 成人性生交xxxxx网站| 国产99久久久欧美黑人| 久久精品99无色码中文字幕| 欧美丰满少妇xxxxx做受| 亚洲第一页自拍| 欧美日韩一区二区精品| 国内精品模特av私拍在线观看| 九九热最新视频//这里只有精品| 日韩欧美精品中文字幕| 久久久久久久国产精品| 久久99精品视频一区97| 亚洲国产天堂网精品网站| 一本一本久久a久久精品综合小说| 日韩精品视频三区| 国产欧美 在线欧美| 成人中文字幕在线观看| 精品久久中文字幕| 成人精品久久一区二区三区| 国产999精品久久久| 国产日韩欧美视频| 岛国av在线不卡| 国产一区视频在线| 欧美亚洲另类制服自拍| 国产精品第一第二| 亚洲国产古装精品网站| 久久99青青精品免费观看| 亚洲va久久久噜噜噜| 国产精品久久久久久久久久99| 久久精品国产精品亚洲| 亚洲a成v人在线观看| 97在线精品国自产拍中文| 国产精品久久久久久影视| 亚洲欧洲午夜一线一品| 国产日韩欧美日韩大片| 午夜精品久久久久久99热| 国产精品视频xxxx| 国产精品视频最多的网站| 亚洲精品98久久久久久中文字幕| 久久久国产视频91| 国产一区二区三区三区在线观看| 亚洲区一区二区| 国产精品一区二区久久国产| 国产视频在线一区二区| 亚洲欧美日韩高清| 日韩欧美极品在线观看| 国产精品电影一区| 久久在精品线影院精品国产| 国产综合福利在线| 国产欧美最新羞羞视频在线观看| 亚洲一区二区久久久久久久| 日韩在线www| 成人激情视频小说免费下载| 亚洲一区二区久久久久久| 日韩欧美在线视频免费观看| 亚洲国产私拍精品国模在线观看| 精品亚洲一区二区三区在线观看| 亚洲精品视频网上网址在线观看| www.久久撸.com| 国产成人一区二区三区电影| 92福利视频午夜1000合集在线观看| 91精品国产91久久久久福利| 亚州av一区二区| 最近中文字幕mv在线一区二区三区四区| 亚洲一区二区三区视频播放| 少妇精69xxtheporn| 久久国产色av| 精品人伦一区二区三区蜜桃免费| 亚洲第一精品自拍| 中文字幕不卡在线视频极品| 国产成人+综合亚洲+天堂| 亚洲精品一区二区久|