大家都知道,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介面檔案。
看到這裡,大家應該基本清楚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其它相關文章!