CGI,FastCGI,PHP-CGI,PHP-FPM
CGI 簡介
CGI全稱是“通用網(wǎng)關(guān)接口”(Common Gateway Interface),它可以讓一個客戶端,從網(wǎng)頁瀏覽器向執(zhí)行在Web服務器上的程序請求數(shù)據(jù)。 CGI描述了客戶端和這個程序之間傳輸數(shù)據(jù)的一種標準。 CGI的一個目的是要獨立于任何語言的,所以CGI可以用任何一種語言編寫,只要這種語言具有標準輸入、輸出和環(huán)境變量。 如php,perl,tcl等。
CGI 的運行原理
上面的這段話理解可能還是比較抽象,下面我們就通過一次 GET 請求為例進行詳細說明。
?
圖2.7 CGI 運行原理示舉例示意圖?
如圖所示,本次請求的流程如下:
FastCGI 簡介
FastCGI是Web服務器和處理程序之間通信的一種協(xié)議, 是CGI的一種改進方案,FastCGI像是一個常駐(long-lived)型的CGI, 它可以一直執(zhí)行,在請求到達時不會花費時間去fork一個進程來處理(這是CGI最為人詬病的fork-and-execute模式)。 正是因為他只是一個通信協(xié)議,它還支持分布式的運算,所以 FastCGI 程序可以在網(wǎng)站服務器以外的主機上執(zhí)行,并且可以接受來自其它網(wǎng)站服務器的請求。
FastCGI 是與語言無關(guān)的、可伸縮架構(gòu)的 CGI 開放擴展,將 CGI 解釋器進程保持在內(nèi)存中,以此獲得較高的性能。 CGI 程序反復加載是 CGI 性能低下的主要原因,如果 CGI 程序保持在內(nèi)存中并接受 FastCGI 進程管理器調(diào)度, 則可以提供良好的性能、伸縮性、Fail-Over 特性等。
FastCGI 工作流程如下:
?
圖2.8 FastCGI 運行原理示舉例示意圖FastCGI 與傳統(tǒng) CGI 模式的區(qū)別之一則是 Web 服務器不是直接執(zhí)行 CGI 程序了,而是通過 Socket 與 FastCGI 響應器(FastCGI 進程管理器)進行交互,也正是由于 FastCGI 進程管理器是基于 Socket 通信的,所以也是分布式的,Web 服務器可以和 CGI 響應器服務器分開部署。Web 服務器需要將數(shù)據(jù) CGI/1.1 的規(guī)范封裝在遵循 FastCGI 協(xié)議包中發(fā)送給 FastCGI 響應器程序。
FastCGI 協(xié)議
可能上面的內(nèi)容理解起來還是很抽象,這是由于第一對FastCGI協(xié)議還沒有一個大概的認識,第二沒有實際代碼的學習。所以需要預先學習下?FastCGI 協(xié)議,不一定需要完全看懂,可大致了解之后,看完本篇再結(jié)合著學習理解消化。
下面結(jié)合 PHP 的 FastCGI 的代碼進行分析,不作特殊說明以下代碼均來自于 PHP 源碼。
FastCGI 消息類型
FastCGI 將傳輸?shù)南⒆隽撕芏囝愋偷膭澐?#xff0c;其結(jié)構(gòu)體定義如下:
typedef enum _fcgi_request_type {FCGI_BEGIN_REQUEST = 1, /* [in] */ FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */ FCGI_END_REQUEST = 3, /* [out] */ FCGI_PARAMS = 4, /* [in] environment variables */ FCGI_STDIN = 5, /* [in] post data */ FCGI_STDOUT = 6, /* [out] response */ FCGI_STDERR = 7, /* [out] errors */ FCGI_DATA = 8, /* [in] filter data (not supported) */ FCGI_GET_VALUES = 9, /* [in] */ FCGI_GET_VALUES_RESULT = 10 /* [out] */ } fcgi_request_type;消息的發(fā)送順序
下圖是一個比較常見消息傳遞流程
?
圖2.9 FastCGI 消息傳遞流程示意圖?
最先發(fā)送的是FCGI_BEGIN_REQUEST,然后是FCGI_PARAMS和FCGI_STDIN,由于每個消息頭(下面將詳細說明)里面能夠承載的最大長度是65535,所以這兩種類型的消息不一定只發(fā)送一次,有可能連續(xù)發(fā)送多次。
FastCGI 響應體處理完畢之后,將發(fā)送FCGI_STDOUT、FCGI_STDERR,同理也可能多次連續(xù)發(fā)送。最后以FCGI_END_REQUEST表示請求的結(jié)束。 需要注意的一點,FCGI_BEGIN_REQUEST和FCGI_END_REQUEST分別標識著請求的開始和結(jié)束,與整個協(xié)議息息相關(guān),所以他們的消息體的內(nèi)容也是協(xié)議的一部分,因此也會有相應的結(jié)構(gòu)體與之對應(后面會詳細說明)。而環(huán)境變量、標準輸入、標準輸出、錯誤輸出,這些都是業(yè)務相關(guān),與協(xié)議無關(guān),所以他們的消息體的內(nèi)容則無結(jié)構(gòu)體對應。
由于整個消息是二進制連續(xù)傳遞的,所以必須定義一個統(tǒng)一的結(jié)構(gòu)的消息頭,這樣以便讀取每個消息的消息體,方便消息的切割。這在網(wǎng)絡通訊中是非常常見的一種手段。
FastCGI 消息頭
如上,FastCGI 消息分10種消息類型,有的是輸入有的是輸出。而所有的消息都以一個消息頭開始。其結(jié)構(gòu)體定義如下:
typedef struct _fcgi_header {unsigned char version; unsigned char type; unsigned char requestIdB1; unsigned char requestIdB0; unsigned char contentLengthB1; unsigned char contentLengthB0; unsigned char paddingLength; unsigned char reserved; } fcgi_header;字段解釋下:
version標識FastCGI協(xié)議版本。?type?標識FastCGI記錄類型,也就是記錄執(zhí)行的一般職能。?requestId標識記錄所屬的FastCGI請求。?contentLength記錄的contentData組件的字節(jié)數(shù)。
關(guān)于上面的xxB1和xxB0的協(xié)議說明:當兩個相鄰的結(jié)構(gòu)組件除了后綴“B1”和“B0”之外命名相同時,它表示這兩個組件可視為估值為B1<<8 + B0的單個數(shù)字。該單個數(shù)字的名字是這些組件減去后綴的名字。這個約定歸納了一個由超過兩個字節(jié)表示的數(shù)字的處理方式。
比如協(xié)議頭中requestId和contentLength表示的最大值就是 65535。
#include <stdio.h> #include <stdlib.h> #include <limits.h>int main() { unsigned char requestIdB1 = UCHAR_MAX; unsigned char requestIdB0 = UCHAR_MAX; printf("%d\n", (requestIdB1 << 8) + requestIdB0); // 65535 }你可能會想到如果一個消息體長度超過65535怎么辦,則分割為多個相同類型的消息發(fā)送即可。
PHP中的CGI實現(xiàn)
PHP的CGI實現(xiàn)了FastCGI協(xié)議,是一個TCP或UDP協(xié)議的服務器接受來自Web服務器的請求, 當啟動時創(chuàng)建TCP/UDP協(xié)議的服務器的socket監(jiān)聽,并接收相關(guān)請求進行處理。隨后就進入了PHP的生命周期: 模塊初始化,sapi初始化,處理PHP請求,模塊關(guān)閉,sapi關(guān)閉等就構(gòu)成了整個CGI的生命周期。
以TCP為例,在TCP的服務端,一般會執(zhí)行這樣幾個操作步驟:
TCP上客戶-服務器事務的時序如圖2.6所示:
?
圖2.6 TCP上客戶-服務器事務的時序?
PHP的CGI實現(xiàn)從cgi_main.c文件的main函數(shù)開始,在main函數(shù)中調(diào)用了定義在fastcgi.c文件中的初始化,監(jiān)聽等函數(shù)。 對比TCP的流程,我們查看PHP對TCP協(xié)議的實現(xiàn),雖然PHP本身也實現(xiàn)了這些流程,但是在main函數(shù)中一些過程被封裝成一個函數(shù)實現(xiàn)。 對應TCP的操作流程,PHP首先會執(zhí)行創(chuàng)建socket,綁定套接字,創(chuàng)建監(jiān)聽:
if (bindpath) {fcgi_fd = fcgi_listen(bindpath, 128); // 實現(xiàn)socket監(jiān)聽,調(diào)用fcgi_init初始化 ... }在fastcgi.c文件中,fcgi_listen函數(shù)主要用于創(chuàng)建、綁定socket并開始監(jiān)聽,它走完了前面所列TCP流程的前三個階段,
if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || ... bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 || listen(listen_socket, backlog) < 0) { ... }當服務端初始化完成后,進程調(diào)用accept函數(shù)進入阻塞狀態(tài),在main函數(shù)中我們看到如下代碼:
while (parent) {do { pid = fork(); // 生成新的子進程 switch (pid) { case 0: // 子進程 parent = 0; ? /* don't catch our signals */ sigaction(SIGTERM, &old_term, 0); // 終止信號 sigaction(SIGQUIT, &old_quit, 0); // 終端退出符 sigaction(SIGINT, &old_int, 0); // 終端中斷符 break; ... default: /* Fine */ running++; break; } while (parent && (running < children)); ? ... while (!fastcgi || fcgi_accept_request(&request) >= 0) { SG(server_context) = (void *) &request; init_request_info(TSRMLS_C); CG(interactive) = 0; ... }如上的代碼是一個生成子進程,并等待用戶請求。在fcgi_accept_request函數(shù)中,程序會調(diào)用accept函數(shù)阻塞新創(chuàng)建的進程。 當用戶的請求到達時,fcgi_accept_request函數(shù)會判斷是否處理用戶的請求,其中會過濾某些連接請求,忽略受限制客戶的請求, 如果程序受理用戶的請求,它將分析請求的信息,將相關(guān)的變量寫到對應的變量中。 其中在讀取請求內(nèi)容時調(diào)用了safe_read方法。如下所示:?[main() -> fcgi_accept_request() -> fcgi_read_request() -> safe_read()]
static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count) { size_t n = 0; do { ... // 省略 對win32的處理 ret = read(req->fd, ((char*)buf)+n, count-n); // 非win版本的讀操作 ... // 省略 } while (n != count); ? }如上對應服務器端讀取用戶的請求數(shù)據(jù)。
在請求初始化完成,讀取請求完畢后,就該處理請求的PHP文件了。 假設此次請求為PHP_MODE_STANDARD則會調(diào)用php_execute_script執(zhí)行PHP文件。 在此函數(shù)中它先初始化此文件相關(guān)的一些內(nèi)容,然后再調(diào)用zend_execute_scripts函數(shù),對PHP文件進行詞法分析和語法分析,生成中間代碼, 并執(zhí)行zend_execute函數(shù),從而執(zhí)行這些中間代碼。關(guān)于整個腳本的執(zhí)行請參見第三節(jié) 腳本的執(zhí)行。
在處理完用戶的請求后,服務器端將返回信息給客戶端,此時在main函數(shù)中調(diào)用的是fcgi_finish_request(&request, 1); fcgi_finish_request函數(shù)定義在fastcgi.c文件中,其代碼如下:
int fcgi_finish_request(fcgi_request *req, int force_close) { int ret = 1; ? if (req->fd >= 0) { if (!req->closed) { ret = fcgi_flush(req, 1); req->closed = 1; } fcgi_close(req, force_close, 1); } return ret; }如上,當socket處于打開狀態(tài),并且請求未關(guān)閉,則會將執(zhí)行后的結(jié)果刷到客戶端,并將請求的關(guān)閉設置為真。 將數(shù)據(jù)刷到客戶端的程序調(diào)用的是fcgi_flush函數(shù)。在此函數(shù)中,關(guān)鍵是在于答應頭的構(gòu)造和寫操作。 程序的寫操作是調(diào)用的safe_write函數(shù),而safe_write函數(shù)中對于最終的寫操作針對win和linux環(huán)境做了區(qū)分, 在Win32下,如果是TCP連接則用send函數(shù),如果是非TCP則和非win環(huán)境一樣使用write函數(shù)。如下代碼:
#ifdef _WIN32 if (!req->tcp) { ret = write(req->fd, ((char*)buf)+n, count-n); } else { ret = send(req->fd, ((char*)buf)+n, count-n, 0); if (ret <= 0) { errno = WSAGetLastError(); } } #else ret = write(req->fd, ((char*)buf)+n, count-n); #endif在發(fā)送了請求的應答后,服務器端將會執(zhí)行關(guān)閉操作,僅限于CGI本身的關(guān)閉,程序執(zhí)行的是fcgi_close函數(shù)。 fcgi_close函數(shù)在前面提的fcgi_finish_request函數(shù)中,在請求應答完后執(zhí)行。同樣,對于win平臺和非win平臺有不同的處理。 其中對于非win平臺調(diào)用的是write函數(shù)。
以上是一個TCP服務器端實現(xiàn)的簡單說明。這只是我們PHP的CGI模式的基礎(chǔ),在這個基礎(chǔ)上PHP增加了更多的功能。?
php-fpm
FastCGI接口方式在腳本解析服務器上啟動一個或者多個守護進程對動態(tài)腳本進行解析,這些進程就是FastCGI進程管理器,或者稱之為FastCGI引擎, spawn-fcgi與PHP-FPM就是支持PHP的兩個FastCGI進程管理器。
FPM(FastCGI 進程管理器)用于替換 PHP FastCGI 的大部分附加功能,對于高負載網(wǎng)站是非常有用的。
它的功能包括:
-
支持平滑停止/啟動的高級進程管理功能;
-
可以工作于不同的 uid/gid/chroot 環(huán)境下,并監(jiān)聽不同的端口和使用不同的 php.ini 配置文件(可取代 safe_mode 的設置);
-
stdout 和 stderr 日志記錄;
-
在發(fā)生意外情況的時候能夠重新啟動并緩存被破壞的 opcode;
-
文件上傳優(yōu)化支持;
-
"慢日志" - 記錄腳本(不僅記錄文件名,還記錄 PHP backtrace 信息,可以使用 ptrace或者類似工具讀取和分析遠程進程的運行數(shù)據(jù))運行所導致的異常緩慢;
-
fastcgi_finish_request()?- 特殊功能:用于在請求完成和刷新數(shù)據(jù)后,繼續(xù)在后臺執(zhí)行耗時的工作(錄入視頻轉(zhuǎn)換、統(tǒng)計處理等);
-
動態(tài)/靜態(tài)子進程產(chǎn)生;
-
基本 SAPI 運行狀態(tài)信息(類似Apache的 mod_status);
-
基于 php.ini 的配置文件。
使用PHP-FPM來控制PHP-CGI的FastCGI進程
什么是PHP-CGI
PHP-CGI是PHP自帶的FastCGI管理器。
啟動PHP-CGI,使用如下命令:
php-cgi -b 127.0.0.1:9000PHP-CGI的不足
1、php-cgi變更php.ini配置后需重啟php-cgi才能讓新的php-ini生效,不可以平滑重啟
2、直接殺死php-cgi進程,php就不能運行了。(PHP-FPM和Spawn-FCGI就沒有這個問題,守護進程會平滑從新生成新的子進程。)
什么是PHP-FPM
PHP-FPM是一個PHP FastCGI管理器,是只用于PHP的,可以在 http://php-fpm.org/download下載得到.
PHP-FPM其實是PHP源代碼的一個補丁,旨在將FastCGI進程管理整合進PHP包中。必須將它patch到你的PHP源代碼中,在編譯安裝PHP后才可以使用。
現(xiàn)在我們可以在最新的PHP 5.3.2的源碼樹里下載得到直接整合了PHP-FPM的分支,據(jù)說下個版本會融合進PHP的主分支去。相對Spawn-FCGI,PHP-FPM在CPU和內(nèi)存方面的控制都更勝一籌,而且前者很容易崩潰,必須用crontab進行監(jiān)控,而PHP-FPM則沒有這種煩惱。
PHP5.3.3已經(jīng)集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP進程管理方式,可以有效控制內(nèi)存和進程、可以平滑重載PHP配置,比spawn-fcgi具有更多有點,所以被PHP官方收錄了。在./configure的時候帶 –enable-fpm參數(shù)即可開啟PHP-FPM。
使用PHP-FPM來控制PHP-CGI的FastCGI進程
/usr/local/php/sbin/php-fpm{start|stop|quit|restart|reload|logrotate}--start 啟動php的fastcgi進程 --stop 強制終止php的fastcgi進程 --quit 平滑終止php的fastcgi進程 --restart 重啟php的fastcgi進程 --reload 重新平滑加載php的php.ini --logrotate 重新啟用log文件轉(zhuǎn)載于:https://www.cnblogs.com/abbiebear/p/9360287.html
總結(jié)
以上是生活随笔為你收集整理的CGI,FastCGI,PHP-CGI,PHP-FPM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql中字符查询与替换
- 下一篇: VMware 安装ubuntu 18.0