深入理解PHP原理之PHP与WEB服务器交互
往期精選(歡迎轉(zhuǎn)發(fā)~~)
- 如何看待程序員35歲職業(yè)危機?
- Java全套學習資料(14W字),耗時半年整理
- 我肝了三個月,為你寫出了GO核心手冊
- 消息隊列:從選型到原理,一文帶你全部掌握
- 肝了一個月的ETCD,從Raft原理到實踐
- 更多…
大家都知道,PHP需要在具體的WEB服務器中才能運行,例如Nginx、Apache等,但是PHP是怎樣啟動,又是怎樣在服務器中運行,然后兩者又是怎樣進行交互的呢?
1.WEB服務器調(diào)用PHP接口
以Apache服務器為例,我們看看該服務器是怎樣啟動PHP,并調(diào)用PHP中的方法。Apache服務器啟動并運行PHP時,一般是通過mod_php7模塊的形式集成(如果是php5.*版本,就是mod_php5模塊,模塊后綴名根據(jù)php版本而定),mod_php7的結構如下(源碼路徑為php/sapi/apache2handler/mod_php7.c):
當Apache需要調(diào)用PHP中的方法時,只需要將該請求通過mod_php7模塊傳達給PHP,PHP層處理完后將數(shù)據(jù)返回給Apache,整個過程就結束了(補充一下:Apache服務器啟動PHP時,其實有兩種加載方式,一種為靜態(tài)加載,一種為動態(tài)加載,剛才討論的mod_php5模塊加載方式可以理解為靜態(tài)加載,也就是需要重新啟動Apache服務器,才能將PHP加載進去;動態(tài)加載不需要重啟服務器,只需要通過發(fā)送信號的方式將PHP固定的模塊加載到服務器,以達到PHP啟動的目的,但是在進行動態(tài)加載前,需要將加載模塊編譯成動態(tài)鏈接庫,然后將其配置到服務器的配置文件中)。上面已經(jīng)給出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個參數(shù),定義如下:
#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定義了模塊的所有指令集合,具體定義內(nèi)容如下(代碼路徑為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個指令,每個指令的實現(xiàn)源碼也在apache_config.c文件中,最后就剩php7_module.php_ap2_register_hook了,它定義的內(nèi)容如下(代碼路徑為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_SIGNALSap_hook_child_init(zend_signal_init, NULL, NULL, APR_HOOK_MIDDLE); #endifap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE); }php7_module.php_ap2_register_hook函數(shù)包含4個鉤子和對應的處理函數(shù),pre_config,pre_config、post_config和child_init是啟動鉤子,它們是在服務器啟動時調(diào)用,handler鉤子是請求掛鉤,它是在服務器請求是調(diào)用,通過這些鉤子,就可以通過Apache服務器啟動PHP。
將到這里,想必大家已經(jīng)知道WEB服務器是如何啟動PHP,并調(diào)用PHP中的方法了哈,下面再給大家講講PHP是如何調(diào)用WEB服務器接口的。
2.PHP調(diào)用WEB服務器接口
在講述這個問題前,我們需要了解一下什么是SAPI。SAPI其實是與服務器抽象層之間遵守的共同約定,可以這么簡單理解,當PHP需要調(diào)用服務器中的方法,例如清除緩存,但是清除緩存的實現(xiàn)方法是在服務器中實現(xiàn),PHP層根本就不知道怎么調(diào)用服務器中的該方法,怎么辦?這時雙方需要進行約定,然后服務器提供一套約定后的接口給PHP,我們把這些與服務器抽象層之間遵守的共同約定稱為SAPI接口。
問題來了,對于服務器Apache,我們可以提供一套SAPI,但是如果下次又來個其它的服務器,或者其它的“第三方”,那么我們是不是也要給他們提供一套單獨的SAPI呢?我們聰明的PHP開發(fā)者肯定想到了這一點,即對所有的“第三方”提供一套通用的SAPI接口,但是你可以會問,如果新的“第三方”需要的接口,你的通用SAPI不支持,那怎么辦呢,我的理解是將新的功能添加到PHP的通用SAPI接口中,僅僅是個人見解哈,通用SAPI結構如下(源碼路徑: php/main/SAPI.h):
該結構體變量較多,就不一一列舉,簡要說明一下里面的變量:startup函數(shù)是當SAPI初始化時會被調(diào)用,shutdown函數(shù)是用來釋放SAPI的數(shù)據(jù)結構和內(nèi)存等,read_cookie 是在SAPI激活時被調(diào)用,然后將此函數(shù)獲取的值賦值給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調(diào)用的“第三方”,該目錄結構如下圖所示,目錄php/sapi/apache2handler中都是與PHP交互的接口,sapi_apache2.c是PHP與Apache約定的SAPI接口文件。
看到這里,大家應該基本清楚PHP層是怎樣調(diào)用服務器層的接口,為了鞏固上面的知識,下面舉個栗子,即在Apache服務器環(huán)境下讀取cookie:
對于任意一個服務器在加載時,我們都會指定sapi_module,Apache的sapi_module是apache2_sapi_module,它的read_cookies方法的是php_apache_sapi_read_cookies函數(shù),這樣就實現(xiàn)PHP層調(diào)用Apache的接口,是不是很簡單呢:)
3.后記
這篇博文是我參考《深入理解PHP內(nèi)核》一書總結的,參考的內(nèi)容為第二章第二節(jié)“SAPI概述”,不過我感覺該書中這部分內(nèi)容講的有點繞,我重新編排了,然后提取了里面的重點,并加入個人見解,如果在該文中有哪些講的不對的地方,希望能幫我指出來,大家共同提高哈,謝謝!
參考: http://www.php-internals.com/
總結
以上是生活随笔為你收集整理的深入理解PHP原理之PHP与WEB服务器交互的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nginx跳转到用户首页
- 下一篇: linux怎么进入自己添加的软盘,lin