CGI,FastCGI,PHP-CGI,PHP-FPM
CGI 簡介
CGI全稱是“通用網關接口”(Common Gateway Interface),它可以讓一個客戶端,從網頁瀏覽器向執行在Web服務器上的程序請求數據。 CGI描述了客戶端和這個程序之間傳輸數據的一種標準。 CGI的一個目的是要獨立于任何語言的,所以CGI可以用任何一種語言編寫,只要這種語言具有標準輸入、輸出和環境變量。 如php,perl,tcl等。
CGI 的運行原理
上面的這段話理解可能還是比較抽象,下面我們就通過一次 GET 請求為例進行詳細說明。
?
圖2.7 CGI 運行原理示舉例示意圖?
如圖所示,本次請求的流程如下:
FastCGI 簡介
FastCGI是Web服務器和處理程序之間通信的一種協議, 是CGI的一種改進方案,FastCGI像是一個常駐(long-lived)型的CGI, 它可以一直執行,在請求到達時不會花費時間去fork一個進程來處理(這是CGI最為人詬病的fork-and-execute模式)。 正是因為他只是一個通信協議,它還支持分布式的運算,所以 FastCGI 程序可以在網站服務器以外的主機上執行,并且可以接受來自其它網站服務器的請求。
FastCGI 是與語言無關的、可伸縮架構的 CGI 開放擴展,將 CGI 解釋器進程保持在內存中,以此獲得較高的性能。 CGI 程序反復加載是 CGI 性能低下的主要原因,如果 CGI 程序保持在內存中并接受 FastCGI 進程管理器調度, 則可以提供良好的性能、伸縮性、Fail-Over 特性等。
FastCGI 工作流程如下:
?
圖2.8 FastCGI 運行原理示舉例示意圖FastCGI 與傳統 CGI 模式的區別之一則是 Web 服務器不是直接執行 CGI 程序了,而是通過 Socket 與 FastCGI 響應器(FastCGI 進程管理器)進行交互,也正是由于 FastCGI 進程管理器是基于 Socket 通信的,所以也是分布式的,Web 服務器可以和 CGI 響應器服務器分開部署。Web 服務器需要將數據 CGI/1.1 的規范封裝在遵循 FastCGI 協議包中發送給 FastCGI 響應器程序。
FastCGI 協議
可能上面的內容理解起來還是很抽象,這是由于第一對FastCGI協議還沒有一個大概的認識,第二沒有實際代碼的學習。所以需要預先學習下?FastCGI 協議,不一定需要完全看懂,可大致了解之后,看完本篇再結合著學習理解消化。
下面結合 PHP 的 FastCGI 的代碼進行分析,不作特殊說明以下代碼均來自于 PHP 源碼。
FastCGI 消息類型
FastCGI 將傳輸的消息做了很多類型的劃分,其結構體定義如下:
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;消息的發送順序
下圖是一個比較常見消息傳遞流程
?
圖2.9 FastCGI 消息傳遞流程示意圖?
最先發送的是FCGI_BEGIN_REQUEST,然后是FCGI_PARAMS和FCGI_STDIN,由于每個消息頭(下面將詳細說明)里面能夠承載的最大長度是65535,所以這兩種類型的消息不一定只發送一次,有可能連續發送多次。
FastCGI 響應體處理完畢之后,將發送FCGI_STDOUT、FCGI_STDERR,同理也可能多次連續發送。最后以FCGI_END_REQUEST表示請求的結束。 需要注意的一點,FCGI_BEGIN_REQUEST和FCGI_END_REQUEST分別標識著請求的開始和結束,與整個協議息息相關,所以他們的消息體的內容也是協議的一部分,因此也會有相應的結構體與之對應(后面會詳細說明)。而環境變量、標準輸入、標準輸出、錯誤輸出,這些都是業務相關,與協議無關,所以他們的消息體的內容則無結構體對應。
由于整個消息是二進制連續傳遞的,所以必須定義一個統一的結構的消息頭,這樣以便讀取每個消息的消息體,方便消息的切割。這在網絡通訊中是非常常見的一種手段。
FastCGI 消息頭
如上,FastCGI 消息分10種消息類型,有的是輸入有的是輸出。而所有的消息都以一個消息頭開始。其結構體定義如下:
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協議版本。?type?標識FastCGI記錄類型,也就是記錄執行的一般職能。?requestId標識記錄所屬的FastCGI請求。?contentLength記錄的contentData組件的字節數。
關于上面的xxB1和xxB0的協議說明:當兩個相鄰的結構組件除了后綴“B1”和“B0”之外命名相同時,它表示這兩個組件可視為估值為B1<<8 + B0的單個數字。該單個數字的名字是這些組件減去后綴的名字。這個約定歸納了一個由超過兩個字節表示的數字的處理方式。
比如協議頭中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怎么辦,則分割為多個相同類型的消息發送即可。
PHP中的CGI實現
PHP的CGI實現了FastCGI協議,是一個TCP或UDP協議的服務器接受來自Web服務器的請求, 當啟動時創建TCP/UDP協議的服務器的socket監聽,并接收相關請求進行處理。隨后就進入了PHP的生命周期: 模塊初始化,sapi初始化,處理PHP請求,模塊關閉,sapi關閉等就構成了整個CGI的生命周期。
以TCP為例,在TCP的服務端,一般會執行這樣幾個操作步驟:
TCP上客戶-服務器事務的時序如圖2.6所示:
?
圖2.6 TCP上客戶-服務器事務的時序?
PHP的CGI實現從cgi_main.c文件的main函數開始,在main函數中調用了定義在fastcgi.c文件中的初始化,監聽等函數。 對比TCP的流程,我們查看PHP對TCP協議的實現,雖然PHP本身也實現了這些流程,但是在main函數中一些過程被封裝成一個函數實現。 對應TCP的操作流程,PHP首先會執行創建socket,綁定套接字,創建監聽:
if (bindpath) {fcgi_fd = fcgi_listen(bindpath, 128); // 實現socket監聽,調用fcgi_init初始化 ... }在fastcgi.c文件中,fcgi_listen函數主要用于創建、綁定socket并開始監聽,它走完了前面所列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) { ... }當服務端初始化完成后,進程調用accept函數進入阻塞狀態,在main函數中我們看到如下代碼:
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函數中,程序會調用accept函數阻塞新創建的進程。 當用戶的請求到達時,fcgi_accept_request函數會判斷是否處理用戶的請求,其中會過濾某些連接請求,忽略受限制客戶的請求, 如果程序受理用戶的請求,它將分析請求的信息,將相關的變量寫到對應的變量中。 其中在讀取請求內容時調用了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); ? }如上對應服務器端讀取用戶的請求數據。
在請求初始化完成,讀取請求完畢后,就該處理請求的PHP文件了。 假設此次請求為PHP_MODE_STANDARD則會調用php_execute_script執行PHP文件。 在此函數中它先初始化此文件相關的一些內容,然后再調用zend_execute_scripts函數,對PHP文件進行詞法分析和語法分析,生成中間代碼, 并執行zend_execute函數,從而執行這些中間代碼。關于整個腳本的執行請參見第三節 腳本的執行。
在處理完用戶的請求后,服務器端將返回信息給客戶端,此時在main函數中調用的是fcgi_finish_request(&request, 1); fcgi_finish_request函數定義在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處于打開狀態,并且請求未關閉,則會將執行后的結果刷到客戶端,并將請求的關閉設置為真。 將數據刷到客戶端的程序調用的是fcgi_flush函數。在此函數中,關鍵是在于答應頭的構造和寫操作。 程序的寫操作是調用的safe_write函數,而safe_write函數中對于最終的寫操作針對win和linux環境做了區分, 在Win32下,如果是TCP連接則用send函數,如果是非TCP則和非win環境一樣使用write函數。如下代碼:
#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在發送了請求的應答后,服務器端將會執行關閉操作,僅限于CGI本身的關閉,程序執行的是fcgi_close函數。 fcgi_close函數在前面提的fcgi_finish_request函數中,在請求應答完后執行。同樣,對于win平臺和非win平臺有不同的處理。 其中對于非win平臺調用的是write函數。
以上是一個TCP服務器端實現的簡單說明。這只是我們PHP的CGI模式的基礎,在這個基礎上PHP增加了更多的功能。?
php-fpm
FastCGI接口方式在腳本解析服務器上啟動一個或者多個守護進程對動態腳本進行解析,這些進程就是FastCGI進程管理器,或者稱之為FastCGI引擎, spawn-fcgi與PHP-FPM就是支持PHP的兩個FastCGI進程管理器。
FPM(FastCGI 進程管理器)用于替換 PHP FastCGI 的大部分附加功能,對于高負載網站是非常有用的。
它的功能包括:
-
支持平滑停止/啟動的高級進程管理功能;
-
可以工作于不同的 uid/gid/chroot 環境下,并監聽不同的端口和使用不同的 php.ini 配置文件(可取代 safe_mode 的設置);
-
stdout 和 stderr 日志記錄;
-
在發生意外情況的時候能夠重新啟動并緩存被破壞的 opcode;
-
文件上傳優化支持;
-
"慢日志" - 記錄腳本(不僅記錄文件名,還記錄 PHP backtrace 信息,可以使用 ptrace或者類似工具讀取和分析遠程進程的運行數據)運行所導致的異常緩慢;
-
fastcgi_finish_request()?- 特殊功能:用于在請求完成和刷新數據后,繼續在后臺執行耗時的工作(錄入視頻轉換、統計處理等);
-
動態/靜態子進程產生;
-
基本 SAPI 運行狀態信息(類似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后才可以使用。
現在我們可以在最新的PHP 5.3.2的源碼樹里下載得到直接整合了PHP-FPM的分支,據說下個版本會融合進PHP的主分支去。相對Spawn-FCGI,PHP-FPM在CPU和內存方面的控制都更勝一籌,而且前者很容易崩潰,必須用crontab進行監控,而PHP-FPM則沒有這種煩惱。
PHP5.3.3已經集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP進程管理方式,可以有效控制內存和進程、可以平滑重載PHP配置,比spawn-fcgi具有更多有點,所以被PHP官方收錄了。在./configure的時候帶 –enable-fpm參數即可開啟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文件轉載于:https://www.cnblogs.com/abbiebear/p/9360287.html
總結
以上是生活随笔為你收集整理的CGI,FastCGI,PHP-CGI,PHP-FPM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql中字符查询与替换
- 下一篇: VMware 安装ubuntu 18.0