當前位置:
首頁 > 最新 > 深入理解 FastCGI 協議以及在 PHP 中的實現

深入理解 FastCGI 協議以及在 PHP 中的實現

在討論 FastCGI 之前,不得不說傳統的 CGI 的工作原理,同時應該大概了解CGI 1.1協議

傳統 CGI 工作原理分析

客戶端訪問某個 URL 地址之後,通過 GET/POST/PUT 等方式提交數據,並通過 HTTP 協議向 Web 伺服器發出請求,伺服器端的 HTTP Daemon(守護進程)將 HTTP 請求里描述的信息通過標準輸入 stdin 和環境變數(environment variable)傳遞給主頁指定的 CGI 程序,並啟動此應用程序進行處理(包括對資料庫的處理),處理結果通過標準輸出 stdout 返回給 HTTP Daemon 守護進程,再由 HTTP Daemon 進程通過 HTTP 協議返回給客戶端。

上面的這段話理解可能還是比較抽象,下面我們就通過一次GET請求為例進行詳細說明。

下面用代碼來實現圖中表述的功能。Web 伺服器啟動一個 socket 監聽服務,然後在本地執行 CGI 程序。後面有比較詳細的代碼解讀。

Web 伺服器代碼#include #include #include #include #include #include #include #include #define SERV_PORT 9003 char* str_join(char *str1, char *str2); char* html_response(char *res, char *buf); int main(void) { int lfd, cfd; struct sockaddr_in serv_addr,clin_addr; socklen_t clin_len; char buf[1024],web_result[1024]; int len; FILE *cin; if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("create socket failed"); exit(1); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_PORT); if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perror("bind error"); exit(1); } if(listen(lfd, 128) == -1) { perror("listen error"); exit(1); } signal(SIGCLD,SIG_IGN); while(1) { clin_len = sizeof(clin_addr); if ((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1) { perror("接收錯誤"); continue; } cin = fdopen(cfd, "r"); setbuf(cin, (char *)0); fgets(buf,1024,cin); //讀取第一行 printf("%s", buf); //============================ cgi 環境變數設置演示 ============================ // 例如 "GET /user.cgi?id=1 HTTP/1.1"; char *delim = " "; char *p; char *method, *filename, *query_string; char *query_string_pre = "QUERY_STRING="; method = strtok(buf,delim); // GET p = strtok(NULL,delim); // /user.cgi?id=1 filename = strtok(p,"?"); // /user.cgi if (strcmp(filename,"/favicon.ico") == 0) { continue; } query_string = strtok(NULL,"?"); // id=1 putenv(str_join(query_string_pre,query_string)); //============================ cgi 環境變數設置演示 ============================ int pid = fork(); if (pid > 0) { close(cfd); } else if (pid == 0) { close(lfd); FILE *stream = popen(str_join(".",filename),"r"); fread(buf,sizeof(char),sizeof(buf),stream); html_response(web_result,buf); write(cfd,web_result,sizeof(web_result)); pclose(stream); close(cfd); exit(0); } else { perror("fork error"); exit(1); } } close(lfd); return 0; } char* str_join(char *str1, char *str2) { char *result = malloc(strlen(str1)+strlen(str2)+1); if (result == NULL) exit (1); strcpy(result, str1); strcat(result, str2); return result; } char* html_response(char *res, char *buf) { char *html_response_template = "HTTP/1.1 200 OK
Content-Type:text/html
Content-Length: %d
Server: mengkang

%s"; sprintf(res,html_response_template,strlen(buf),buf); return res; }如上代碼中的重點:

66~81行找到CGI程序的相對路徑(我們為了簡單,直接將其根目錄定義為Web程序的當前目錄),這樣就可以在子進程中執行 CGI 程序了;同時設置環境變數,方便CGI程序運行時讀取;

94~95行將 CGI 程序的標準輸出結果寫入 Web 伺服器守護進程的緩存中;

97行則將包裝後的 html 結果寫入客戶端 socket 描述符,返回給連接Web伺服器的客戶端。

CGI 程序(user.c)#include #include // 通過獲取的 id 查詢用戶的信息 int main(void){ //============================ 模擬資料庫 ============================ typedef struct { int id; char *username; int age; } user; user users[] = { {}, { 1, "mengkang.zhou", 18 } }; //============================ 模擬資料庫 ============================ char *query_string; int id; query_string = getenv("QUERY_STRING"); if (query_string == NULL) { printf("沒有輸入數據"); } else if (sscanf(query_string,"id=%d",&id) != 1) { printf("沒有輸入id"); } else { printf("用戶信息查詢

學號: %d

姓名: %s

年齡: %d",id,users[id].username,users[id].age); } return 0; }

將上面的 CGI 程序編譯成 ,放在上面web程序的同級目錄。

代碼中的第28行,從環境變數中讀取前面在Web伺服器守護進程中設置的環境變數,是我們演示的重點。

FastCGI 工作原理分析

相對於 CGI/1.1 規範在 Web 伺服器在本地 fork 一個子進程執行 CGI 程序,填充 CGI 預定義的環境變數,放入系統環境變數,把 HTTP body 體的 content 通過標準輸入傳入子進程,處理完畢之後通過標準輸出返回給 Web 伺服器。FastCGI 的核心則是取締傳統的 fork-and-execute 方式,減少每次啟動的巨大開銷(後面以 PHP 為例說明),以常駐的方式來處理請求。

FastCGI 工作流程如下:

FastCGI 進程管理器自身初始化,啟動多個 CGI 解釋器進程,並等待來自 Web Server 的連接。

Web 伺服器與 FastCGI 進程管理器進行 Socket 通信,通過 FastCGI 協議發送 CGI 環境變數和標準輸入數據給 CGI 解釋器進程。

CGI 解釋器進程完成處理後將標準輸出和錯誤信息從同一連接返回 Web Server。

CGI 解釋器進程接著等待並處理來自 Web Server 的下一個連接。

FastCGI 與傳統 CGI 模式的區別之一則是 Web 伺服器不是直接執行 CGI 程序了,而是通過 socket 與 FastCGI 響應器(FastCGI 進程管理器)進行交互,Web 伺服器需要將 CGI 介面數據封裝在遵循 FastCGI 協議包中發送給 FastCGI 響應器程序。正是由於 FastCGI 進程管理器是基於 socket 通信的,所以也是分布式的,Web伺服器和CGI響應器伺服器分開部署。

再啰嗦一句,FastCGI 是一種協議,它是建立在CGI/1.1基礎之上的,把CGI/1.1裡面的要傳遞的數據通過FastCGI協議定義的順序、格式進行傳遞。

準備工作

可能上面的內容理解起來還是很抽象,這是由於第一對FastCGI協議還沒有一個大概的認識,第二沒有實際代碼的學習。所以需要預先學習下 FastCGI 協議的內容,不一定需要完全看懂,可大致了解之後,看完本篇再結合著學習理解消化。

http://andylin02.iteye.com/bl...(中文版)

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;消息的發送順序

下圖是一個簡單的消息傳遞流程

最先發送的是 ,然後是 和 ,由於每個消息頭(下面將詳細說明)裡面能夠承載的最大長度是65535,所以這兩種類型的消息不一定只發送一次,有可能連續發送多次。

FastCGI 響應體處理完畢之後,將發送 、 ,同理也可能多次連續發送。最後以 表示請求的結束。

需要注意的一點, 和 分別標識著請求的開始和結束,與整個協議息息相關,所以他們的消息體的內容也是協議的一部分,因此也會有相應的結構體與之對應(後面會詳細說明)。而環境變數、標準輸入、標準輸出、錯誤輸出,這些都是業務相關,與協議無關,所以他們的消息體的內容則無結構體對應。

由於整個消息是二進位連續傳遞的,所以必須定義一個統一的結構的消息頭,這樣以便讀取每個消息的消息體,方便消息的切割。這在網路通訊中是非常常見的一種手段。

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;

欄位解釋下:

標識FastCGI協議版本。

標識FastCGI記錄類型,也就是記錄執行的一般職能。

標識記錄所屬的FastCGI請求。

記錄的contentData組件的位元組數。

關於上面的 和 的協議說明:當兩個相鄰的結構組件除了後綴「B1」和「B0」之外命名相同時,它表示這兩個組件可視為估值為B1<903531b733f7950e91c191769d673dec + B0的單個數字。該單個數字的名字是這些組件減去後綴的名字。這個約定歸納了一個由超過兩個位元組表示的數字的處理方式。

比如協議頭中 和 表示的最大值就是

#include #include #include int main() { unsigned char requestIdB1 = UCHAR_MAX; unsigned char requestIdB0 = UCHAR_MAX; printf("%d", (requestIdB1

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

马化腾VS朱啸虎:自爆微信支付数据妥不妥当?
Pyenv 使用筆記
為什麼 Kotlin 調用 java 時可以使用 Lambda?——Kotlin中SAM 轉換機制詳解
鎚子銷售額大增3006%,小米榮耀死磕第1,京東618排行榜上,最受傷的竟是魅族
新產品管理:神秘的「戰略」到底是什麼?

TAG:推酷 |

您可能感興趣

深入理解 RPC:基於 Python 自建分散式高並發 RPC 服務
何愷明CVPR演講:深入理解ResNet和視覺識別的表示學習(41 PPT)
如何理解 NVIDIA新GPU 架構 Turing的Tensor Core?
深入理解 ES Modules
更深入理解 Python 中的迭代
這張海報 融入了OPPO Find X對極致的理解
如何理解華為榮耀的CPU Turbo和GPU Turbo?
理解並實現 ResNet(Keras)
深入理解 Web Server 原理與實踐:Nginx
何愷明CVPR演講:深入理解ResNet和視覺識別的表示學習
PHP HTTP客戶端-Guzzle原理解析
我所理解的 Smartisan OS
Redhat Ceph存儲之「深入理解Ceph架構」
一文解構PyTorch:40頁PPT理解內部機制
代碼詳解:通過模擬API來理解TensorFlow
Python中 Flask的魔法方法深入理解
深入理解Flask
UNC&Adobe提出模塊化注意力模型MAttNet,解決指示表達的理解問題
對話 IJCAI 07「卓越研究獎」得主 Alan Bundy :理解智能的本質是 AI 發展的終極目標
MapReduce Shuffle深入理解