深入理解PHP與WEB伺服器互動

2020-07-16 10:05:41

  大家都知道,PHP需要在具體的WEB伺服器中才能執行,例如Nginx、Apache等,但是PHP是怎樣啟動,又是怎樣在伺服器中執行,然後兩者又是怎樣進行互動的呢?

推薦教學:php視訊教學

1.WEB伺服器呼叫PHP介面

  以Apache伺服器為例,我們看看該伺服器是怎樣啟動PHP,並呼叫PHP中的方法。Apache伺服器啟動並執行PHP時,一般是通過mod_php7模組的形式整合(如果是php5.*版本,就是mod_php5模組,模組字尾名根據php版本而定),mod_php7的結構如下(原始碼路徑為php/sapi/apache2handler/mod_php7.c):

AP_MODULE_DECLARE_DATA module php7_module = {
    STANDARD20_MODULE_STUFF,/* 宏,包括版本,版本,模組索引,模組名,下個模組指標等資訊 */
    create_php_config,      /* create per-directory config structure */
    merge_php_config,       /* merge per-directory config structures */
    NULL,                   /* create per-server config structure */
    NULL,                   /* merge per-server config structures */
    php_dir_cmds,           /* 模組定義的所有指令 */
    php_ap2_register_hook   /* register hooks */
};

  當Apache需要呼叫PHP中的方法時,只需要將該請求通過mod_php7模組傳達給PHP,PHP層處理完後將資料返回給Apache,整個過程就結束了(補充一下:Apache伺服器啟動PHP時,其實有兩種載入方式,一種為靜態載入,一種為動態載入,剛才討論的mod_php5模組載入方式可以理解為靜態載入,也就是需要重新啟動Apache伺服器,才能將PHP載入進去;動態載入不需要重新啟動伺服器,只需要通過傳送信號的方式將PHP固定的模組載入到伺服器,以達到PHP啟動的目的,但是在進行動態載入前,需要將載入模組編譯成動態連結庫,然後將其設定到伺服器的組態檔中)。上面已經給出Apache在PHP中的model結構,下面給出Apache伺服器中對應的module結構,如下(該原始碼在Apache中,下同):

struct module_struct {
    int version;
    int minor_version;
    int module_index;
    const char *name;
    void *dynamic_load_handle;
    struct module_struct *next;
    unsigned long magic;
    void (*rewrite_args) (process_rec *process);
    void *(*create_dir_config) (apr_pool_t *p, char *dir);
    void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    void *(*create_server_config) (apr_pool_t *p, server_rec *s);
    void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    const command_rec *cmds;
    void (*register_hooks) (apr_pool_t *p);
}

  可以看得出php7_module和module_struct還是有很大不同,不過如果看到php7_module.STANDARD20_MODULE_STUFF這個宏的定義方式,你可能就會覺得這兩個結構體很像,其實這個宏定義了module_struct中的前8個引數,定義如下:

#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, 
    MODULE_MAGIC_NUMBER_MINOR, 
    -1, 
    __FILE__, 
    NULL, 
    NULL, 
    MODULE_MAGIC_COOKIE, 
    NULL /* rewrite args spot */

  然後php7_module.php_dir_cmds定義了模組的所有指令集合,具體定義內容如下(程式碼路徑為php/sapi/apache2handler/apache_config.c):

const command_rec php_dir_cmds[] =
{
    AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL, OR_OPTIONS, "PHP Value Modifier"),
    AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL, OR_OPTIONS, "PHP Flag Modifier"),
    AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)
    "),
    AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
    AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL, RSRC_CONF, "Directory containing the php.ini file"),
    {NULL}
};

  也就是說,PHP層只給Apache提供了上述5個指令,每個指令的實現原始碼也在apache_config.c檔案中,最後就剩php7_module.php_ap2_register_hook了,它定義的內容如下(程式碼路徑為php/sapi/apache2handler/mod_php7.c):

void php_ap2_register_hook(apr_pool_t *p)
{
    ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
#ifdef ZEND_SIGNALS
    ap_hook_child_init(zend_signal_init, NULL, NULL, APR_HOOK_MIDDLE);
#endif
    ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}

  php7_module.php_ap2_register_hook函數包含4個勾點和對應的處理常式,pre_config,pre_config、post_config和child_init是啟動勾點,它們是在伺服器啟動時呼叫,handler勾點是請求掛鉤,它是在伺服器請求是呼叫,通過這些勾點,就可以通過Apache伺服器啟動PHP。

   將到這裡,想必大家已經知道WEB伺服器是如何啟動PHP,並呼叫PHP中的方法了哈,下面再給大家講講PHP是如何呼叫WEB伺服器介面的。

2.PHP呼叫WEB伺服器介面

  在講述這個問題前,我們需要了解一下什麼是SAPI。SAPI其實是與伺服器抽象層之間遵守的共同約定,可以這麼簡單理解,當PHP需要呼叫伺服器中的方法,例如清除快取,但是清除快取的實現方法是在伺服器中實現,PHP層根本就不知道怎麼呼叫伺服器中的該方法,怎麼辦?這時雙方需要進行約定,然後伺服器提供一套約定後的介面給PHP,我們把這些與伺服器抽象層之間遵守的共同約定稱為SAPI介面。

  問題來了,對於伺服器Apache,我們可以提供一套SAPI,但是如果下次又來個其它的伺服器,或者其它的「第三方」,那麼我們是不是也要給他們提供一套單獨的SAPI呢?我們聰明的PHP開發者肯定想到了這一點,即對所有的「第三方」提供一套通用的SAPI介面,但是你可以會問,如果新的「第三方」需要的介面,你的通用SAPI不支援,那怎麼辦呢,我的理解是將新的功能新增到PHP的通用SAPI介面中,僅僅是個人見解哈,通用SAPI結構如下(原始碼路徑: php/main/SAPI.h):

struct _sapi_module_struct {
    char *name;         // 名字
    char *pretty_name;  // 更好理解的名字
    int (*startup)(struct _sapi_module_struct *sapi_module);    // 啟動函數
    int (*shutdown)(struct _sapi_module_struct *sapi_module);   // 關閉函數
    int (*activate)(TSRMLS_D);           // 啟用
    int (*deactivate)(TSRMLS_D);         // 停用
    void (*flush)(void *server_context); // flush
    char *(*read_cookies)(TSRMLS_D);     //read Cookies
    //...
};

  該結構體變數較多,就不一一列舉,簡要說明一下裡面的變數:startup函數是當SAPI初始化時會被呼叫,shutdown函數是用來釋放SAPI的資料結構和記憶體等,read_cookie 是在SAPI啟用時被呼叫,然後將此函數獲取的值賦值給SG(request_info).cookie_data。那麼對於PHP提供的通用SAPI,Apache伺服器又是怎樣客製化自己的介面呢?具體結構如下(原始碼路徑為php/sapi/apache2handler/sapi_apache2.c):

static sapi_module_struct apache2_sapi_module = {
    "apache2handler",
    "Apache 2.0 Handler",

    php_apache2_startup,            /* startup */
    php_module_shutdown_wrapper,    /* shutdown */

    NULL,                           /* activate */
    NULL,                           /* deactivate */

    php_apache_sapi_ub_write,       /* unbuffered write */
    php_apache_sapi_flush,          /* flush */
    php_apache_sapi_get_stat,       /* get uid */
    php_apache_sapi_getenv,         /* getenv */

    php_error,                      /* error handler */

    php_apache_sapi_header_handler, /* header handler */
    php_apache_sapi_send_headers,   /* send headers handler */
    NULL,                           /* send header handler */

    php_apache_sapi_read_post,      /* read POST data */
    php_apache_sapi_read_cookies,   /* read Cookies */

    php_apache_sapi_register_variables,
    php_apache_sapi_log_message,        /* Log message */
    php_apache_sapi_get_request_time,   /* Request Time */
    NULL,                               /* Child Terminate */

    STANDARD_SAPI_MODULE_PROPERTIES
};

  上述原始碼目錄php/sapi/apache2handler/中,目錄php/sapi下面放的都是通過SAPI呼叫的「第三方」,該目錄結構如下圖所示,目錄php/sapi/apache2handler中都是與PHP互動的介面,sapi_apache2.c是PHP與Apache約定的SAPI介面檔案。

20160821163330607 (1).jpg

  看到這裡,大家應該基本清楚PHP層是怎樣呼叫伺服器層的介面,為了鞏固上面的知識,下面舉個栗子,即在Apache伺服器環境下讀取cookie:

SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);

對於任意一個伺服器在載入時,我們都會指定sapi_module,Apache的sapi_module是apache2_sapi_module,它的read_cookies方法的是php_apache_sapi_read_cookies函數,這樣就實現PHP層呼叫Apache的介面,是不是很簡單呢:)

3.後記

  這篇博文是我參考《深入理解PHP核心》一書總結的,參考的內容為第二章第二節「SAPI概述」,不過我感覺該書中這部分內容講的有點繞,我重新編排了,然後提取了裡面的重點,並加入個人見解,如果在該文中有哪些講的不對的地方,希望能幫我指出來,大家共同提高哈,謝謝!

原文地址:https://blog.csdn.net/lml200701158/article/details/52267573

以上就是深入理解PHP與WEB伺服器互動的詳細內容,更多請關注TW511.COM其它相關文章!