摘要:深入協議從功能上來講,協議已經完全能夠解決服務器與應用之間的數據通信問題。消息頭信息主要的消息頭信息如下用于表示協議版本號。從服務器發送到應用,表示中止一個處理中的請求。另外我們還需要明確一點就是服務器與進程間通信是無序的。
本文首發于 深入剖析 Web 服務器與 PHP 應用之間的通信機制 - 掌握 CGI 和 FastCGI 協議的運行原理,轉載請注明出處!
身為一名使用 PHP 語言開發后端服務的程序猿,我們每天都和 PHP 以及 Web 服務器產生無數次的親密接觸。得益于它們,我們才能夠如此快速的構建出令人陶醉的 Web 產品。
盡管我們已經和 Web 服務器和 PHP 建立起深厚的友誼,但你知道它們之間為何能夠配合的如此默契么?
這一切都需要從 CGI(Common Gateway Interface:通用網關接口)協議說起。但是請不要對 CGI 協議產生任何的恐懼心理,它并非什么特別復雜的協議,如果你對它不甚了解,可能的原因或許是你還有花一點小心思來學習它。
所以,你應該明白,現在你應該抽出 20 多分鐘仔細的研究一下: Web 服務器與 PHP 應用之間是如何進行通信的這個問題。
介紹我們知道 PHP 自 5.4 起為我們內置的 Web 服務器。不過在此之前的版本(或者不使用這個內置服務器時),我們就需要使用其他的 Web 服務器,通常是 Nginx 或者 Apache 這兩塊 Web 服務器,來部署我們的 PHP 應用。
這就涉及一個問題,當用戶發起一個 HTTP 請求后,我們的 PHP 應用程序在處理這個請求時并沒有直接的解析這個 HTTP 協議,而是可以直接從 $_GET、$_POST 和 $_SERVER等全局變量中,獲取到用戶請求數據和其它系統環境。這究竟又是為何呢?
要想整明白這個問題,我們就不得不需要整明白一個問題:CGI 協議。
CGI 協議同 HTTP 協議一樣是一個「應用層」協議,它的 功能 是為了解決 Web 服務器與 PHP 應用(或其他 Web 應用)之間的通信問題。
既然它是一個「協議」,換言之它與語言無關,即只要是實現類 CGI 協議的應用就能夠實現相互的通信。
深入 CGI 協議我們已經知道了 CGI 協議是為了完成 Web 服務器和應用之間進行數據通信這個問題。那么,這一節我們就來看看究竟它們之間是如何進行通信的。
簡單來講 CGI 協議它描述了 Web 服務器和應用程序之間進行數據傳輸的格式,并且只要我們的編程語言支持標準輸入(STDIN)、標準輸出(STDOUT)以及環境變量等處理,你就可以使用它來編寫一個 CGI 程序。
CGI 的運行原理當用戶訪問我們的 Web 應用時,會發起一個 HTTP 請求。最終 Web 服務器接收到這個請求。
Web 服務器創建一個新的 CGI 進程。在這個進程中,將 HTTP 請求數據已一定格式解析出來,并通過標準輸入和環境變量傳入到 URL 指定的 CGI 程序(PHP 應用 $_SERVER)。
Web 應用程序處理完成后將返回數據寫入到標準輸出中,Web 服務器進程則從標準輸出流中讀取到響應,并采用 HTTP 協議返回給用戶響應。
一句話就是 Web 服務器中的 CGI 進程將接收到的 HTTP 請求數據讀取到環境變量中,通過標準輸入轉發給 PHP 的 CGI 程序;當 PHP 程序處理完成后,Web 服務器中的 CGI 進程從標準輸出中讀取返回數據,并轉換回 HTTP 響應消息格式,最終將頁面呈獻給用戶。然后 Web 服務器關閉掉這個 CGI 進程。
可以說 CGI 協議特別擅長處理 Web 服務器和 Web 應用的通信問題。然而,它有一個嚴重缺陷,對于每個請求都需要重新 fork 出一個 CGI 進程,處理完成后立即關閉。
CGI 協議的缺陷每次處理用戶請求,都需要重新 fork CGI 子進程、銷毀 CGI 子進程。
一系列的 I/O 開銷降低了網絡的吞吐量,造成了資源的浪費,在大并發時會產生嚴重的性能問題。
深入 FastCGI 協議從功能上來講,CGI 協議已經完全能夠解決 Web 服務器與 Web 應用之間的數據通信問題。但是由于每個請求都需要重新 fork 出 CGI 子進程導致性能堪憂,所以基于 CGI 協議的基礎上做了改進便有了 FastCGI 協議,它是一種常駐型的 CGI 協議。
本質上來將 FastCGI 和 CGI 協議幾乎完全一樣,它們都可以從 Web 服務器里接收到相同的數據,不同之處在于采取了不同的通信方式。
再來回顧一下 CGI 協議每次接收到 HTTP 請求時,都需要經歷 fork 出 CGI 子進程、執行處理并銷毀 CGI 子進程這一系列工作。
而 FastCGI 協議采用 進程間通信(IPC) 來處理用戶的請求,下面我們就來看看它的運行原理。
FastCGI 協議運行原理FastCGI 進程管理器啟動時會創建一個 主(Master) 進程和多個 CGI 解釋器進程(Worker 進程),然后等待 Web 服務器的連接。
Web 服務器接收 HTTP 請求后,將 CGI 報文通過 套接字(UNIX 或 TCP Socket)進行通信,將環境變量和請求數據寫入標準輸入,轉發到 CGI 解釋器進程。
CGI 解釋器進程完成處理后將標準輸出和錯誤信息從同一連接返回給 Web 服務器。
CGI 解釋器進程等待下一個 HTTP 請求的到來。
為什么是 FastCGI 而非 CGI 協議如果僅僅因為工作模式的不同,似乎并沒有什么大不了的。并沒到非要選擇 FastCGI 協議不可的地步。
然而,對于這個看似微小的差異,但意義非凡,最終的結果是實現出來的 Web 應用架構上的差異。
CGI 與 FastCGI 架構在 CGI 協議中,Web 應用的生命周期完全依賴于 HTTP 請求的聲明周期。
對每個接收到的 HTTP 請求,都需要重啟一個 CGI 進程來進行處理,處理完成后必須關閉 CGI 進程,才能達到通知 Web 服務器本次 HTTP 請求處理完成的目的。
但是在 FastCGI 中完全不一樣。
FastCGI 進程是常駐型的,一旦啟動就可以處理所有的 HTTP 請求,而無需直接退出。
再看 FastCGI 協議通過前面的講解,我們相比已經可以很準確的說出來 FastCGI 是一種通信協議 這樣的結論。現在,我們就將關注的焦點挪到協議本身,來看看這個協議的定義。
同 HTTP 協議一樣,FastCGI 協議也是有消息頭和消息體組成。
消息頭信息主要的消息頭信息如下:
Version:用于表示 FastCGI 協議版本號。
Type:用于標識 FastCGI 消息的類型 - 用于指定處理這個消息的方法。
RequestID:標識出當前所屬的 FastCGI 請求。
Content Length: 數據包包體所占字節數。
消息類型定義BEGIN_REQUEST:從 Web 服務器發送到 Web 應用,表示開始處理新的請求。
ABORT_REQUEST:從 Web 服務器發送到 Web 應用,表示中止一個處理中的請求。比如,用戶在瀏覽器發起請求后按下瀏覽器上的「停止按鈕」時,會觸發這個消息。
END_REQUEST:從 Web 應用發送給 Web 服務器,表示該請求處理完成。返回數據包里包含「返回的代碼」,它決定請求是否成功處理。
PARAMS:「流數據包」,從 Web 服務器發送到 Web 應用。此時可以發送多個數據包。發送結束標識為從 Web 服務器發出一個長度為 0 的空包。且 PARAMS 中的數據類型和 CGI 協議一致。即我們使用 $_SERVER 獲取到的系統環境等。
STDIN:「流數據包」,用于 Web 應用從標準輸入中讀取出用戶提交的 POST 數據。
STDOUT:「流數據報」,從 Web 應用寫入到標準輸出中,包含返回給用戶的數據。
Web 服務器和 FastCGI 交互過程Web 服務器接收用戶請求,但最終處理請求由 Web 應用完成。此時,Web 服務器嘗試通過套接字(UNIX 或 TCP 套接字,具體使用哪個由 Web 服務器配置決定)連接到 FastCGI 進程。
FastCGI 進程查看接收到的連接。選擇「接收」或「拒絕」連接。如果是「接收」連接,則從標準輸入流中讀取數據包。
如果 FastCGI 進程在指定時間內沒有成功接收到連接,則該請求失敗。否則,Web 服務器發送一個包含唯一的 RequestID 的 BEGIN_REQUEST 類型消息給到 FastCGI 進程。后續所有數據包發送都包含這個 RequestID。
然后,Web 服務器發送任意數量的 PARAMS 類型消息到 FastCGI 進程。一旦發送完畢,Web 服務器通過發送一個空 PARAMS 消息包,然后關閉這個流。
另外,如果用戶發送了 POST 數據 Web 服務器會將其寫入到 標準輸入(STDIN) 發送給 FastCGI 進程。當所有 POST 數據發送完成,會發送一個空的 標準輸入(STDIN) 來關閉這個流。
同時,FastCGI 進程接收到 BEGIN_REQUEST 類型數據包。它可以通過響應 END_REQUEST 來拒絕這個請求。或者接收并處理這個請求。如果接收請求,FastCGI 進程會等待接收所有的 PARAMS 和 標準輸入數據包。
然后,在處理請求并將返回結果寫入 標準輸出(STDOUT) 流。處理完成后,發送一個空的數據包到標準輸出來關閉這個流,并且會發送一個 END_REQUEST 類型消息通知 Web 服務器,告知它是否發生錯誤異常。
如果是每個連接僅處理一個請求,發送 RequestID 則略顯多余。
但是我們的 Web 服務器和 FastCGI 進程之間的連接可能處理多個請求,即一個連接可以處理多個請求。所以才需要采用數據包協議而不是直接使用單個數據流的原因:以實現「多路復用」。
因此,由于每個數據包都包含唯一的 RequestID,所以 Web 服務器才能在一個連接上發送任意數量的請求,并且 FastCGI 進程也能夠從一個連接上接收到任意數量的請求數據包。
另外我們還需要明確一點就是 Web 服務器 與 FastCGI 進程間通信是 無序的。即使我們在交互過程中看起來一個請求是有序的,但是我們的 Web 服務器也有可能在同一時間發出幾十個 BEGIN_REQUEST 類型的數據包,以此類推。
PHP-FPM其實講解完 CGI 和 FastCGI 協議,基本上我們就已經研究完 「Web 服務器與 PHP 應用之間的通信機制」這個問題了。但是對于我們 PHP 軟件工程師來講,可能還會遇到「什么是 PHP-FPM」及其相關問題。這里我們一并來稍微講解一下。
PHP-FPM 是 FastCGI 進程管理器(PHP FastCGI Process Manager),用于替換 PHP 內核的 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-FPM 進程管理器有兩種進程組成,一個 Master 進程和多個 Worker 進程。Master 進程負責監聽端口,接收來自 Web 服務器的請求,然后指派具體的 Worker 進程處理請求;worker 進程則一般有多個 (依據配置決定進程數),每個進程內部都嵌入了一個 PHP 解釋器,用來執行 PHP 代碼。
Nginx 服務器如何與 FastCGI 協同工作Nginx 服務器無法直接與 FastCGI 服務器進行通信,需要啟用 ngx_http_fastcgi_module 模塊進行代理配置,才能將請求發送給 FastCGI 服務。
其中,包括我們熟知的配置指令:
fastcgi_pass 用于設置 FastCGI 服務器的 IP 地址(TCT 套接字)或 UNIX 套接字。
fastcgi_param 設置傳入 FastCGI 服務器的參數。
你可以到 PHP FastCGI 實例教程 學習一些基本使用。
總結到這里我們基本就學習完 CGI、FastCGI、PHP-FPM以及 Nginx 服務器與 FastCGI 服務通信原理。一句話:
CGI 和 FastCGI 是一種協議和 HTTP 協議一樣位于應用層,與語言無關;PHP-FPM 是一種 FastCGI 協議的實現,能夠管理 FastCGI 進程。
擴展閱讀https://blog.cuiyongjian.com/...
https://zhuanlan.zhihu.com/p/...
https://stackoverflow.com/que...
https://paper.seebug.org/289/
https://blog.csdn.net/shreck6...
http://www.phppan.com/2011/05...
https://github.com/pangudashu...
https://github.com/YuanLianDu...
http://blog.51cto.com/1358182...
http://haiyangxu.github.io/po...
https://www.cnblogs.com/xuewe...
https://www.zybuluo.com/phper...
https://www.awaimai.com/371.html
http://tiankonguse.com/blog/?...
https://www.digitalocean.com/...
http://www.whizkidtech.redpri...
https://fastcgi-archives.gith...
http://php.net/manual/zh/inst...
http://php.net/manual/zh/rese...
http://www.php-internals.com/...
https://www.yanxurui.cc/posts...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29111.html
摘要:是與之間數據交換的一種協議。當收到這個請求后,會啟動對應的程序,這里就是的解析器。接下來解析器會解析文件,初始化執行環境,然后處理請求,再以規定規定的格式返回處理后的結果,退出進程,再把結果返回給瀏覽器。 CGI:是 Web Server 與 Web Application 之間數據交換的一種協議。FastCGI:同 CGI,是一種通信協議,但比 CGI 在效率上做了一些優化。PHP-...
摘要:是與之間數據交換的一種協議。當收到這個請求后,會啟動對應的程序,這里就是的解析器。接下來解析器會解析文件,初始化執行環境,然后處理請求,再以規定規定的格式返回處理后的結果,退出進程,再把結果返回給瀏覽器。 CGI:是 Web Server 與 Web Application 之間數據交換的一種協議。FastCGI:同 CGI,是一種通信協議,但比 CGI 在效率上做了一些優化。PHP-...
摘要:是與之間數據交換的一種協議。當收到這個請求后,會啟動對應的程序,這里就是的解析器。接下來解析器會解析文件,初始化執行環境,然后處理請求,再以規定規定的格式返回處理后的結果,退出進程,再把結果返回給瀏覽器。 CGI:是 Web Server 與 Web Application 之間數據交換的一種協議。FastCGI:同 CGI,是一種通信協議,但比 CGI 在效率上做了一些優化。PHP-...
摘要:要說與是如何協同工作的,首先得說和這兩個協議。之于標準的,也提供了一些增強功能,具體可以參考官方文檔。為了能夠使理解協議,提供了模塊來將請求映射為對應的請求。 網絡上有很多關于如何配置 Nginx + FPM 的文章,但它們更多從操作的角度出發,告訴我們怎么做,但卻沒有告訴我們為什么要這么做,本文從 Nginx 與 FPM 的工作機制出發,探討配置背后的原理,讓我們真正理解 Nginx...
摘要:要說與是如何協同工作的,首先得說和這兩個協議。之于標準的,也提供了一些增強功能,具體可以參考官方文檔。為了能夠使理解協議,提供了模塊來將請求映射為對應的請求。 網絡上有很多關于如何配置 Nginx + FPM 的文章,但它們更多從操作的角度出發,告訴我們怎么做,但卻沒有告訴我們為什么要這么做,本文從 Nginx 與 FPM 的工作機制出發,探討配置背后的原理,讓我們真正理解 Nginx...
閱讀 2066·2021-10-12 10:12
閱讀 788·2021-09-24 09:47
閱讀 1187·2021-08-19 11:12
閱讀 3461·2019-08-29 13:06
閱讀 681·2019-08-26 11:43
閱讀 2563·2019-08-23 17:20
閱讀 1146·2019-08-23 16:52
閱讀 2593·2019-08-23 14:27