標籤:blog class code tar ext int
|=-----------------------------------------------------------------------=||=------------------------=[ 深入理解FastCGI ]=--------------------------=||=-----------------------------------------------------------------------=||=--------------------------=[ by d4shman ]=---------------------------=||=-----------------------------------------------------------------------=||=-------------------------=[ May 7, 2014 ]=---------------------------=||=-----------------------------------------------------------------------=|[目錄]0x01 什麼是FastCGI0x02 FastCGI的工作流程0x03 PHP中的CGI實現0x04 參考文獻0x01 什麼是FastCGI CGI全稱是“通用閘道介面”( Common Gateway Interface),它可以讓一個用戶端從網頁瀏覽器向執行在web伺服器上的程式請求資料。CGI描述了用戶端和這個程式之間傳遞資料的一種標準。 FastCGI是web伺服器和處理常式之間通訊的一種協議, 是CGI的一種改進方案, FastCGI像是一個常駐(long live)型的CGI, 它可以一直執行,在請求到達時不會花費時間去fork一個進程來處理(這是CGI最為人詬病的fork-and-execute模式)。 CGI程式反覆載入是CGI效能低下的主要原因,FastCGI將CGI解譯器進程保持在記憶體內中,以此獲得較高的效能。同時,FastCGI還支援分散式運算,也就是說,Web Server和FastCGI可以部署在不同的伺服器上。0x02 FastCGI的工作流程 1.Web server啟動時載入FastCGI進程管理器(Apache Module、IIS ISAPI等) 2.FastCGI進程管理器自身初始化,啟動多個CGI解譯器進程php-cgi並等待來自 Web Server的串連。 3.當用戶端的請求到達Web Server時,FastCGI選擇並串連到一個CGI解譯器。 Web server將CGI環境變數和標準輸入發送到FastCGI子進程php-cgi。 4.FastCGI子進程完成處理後將標準輸出和錯誤資訊從同一串連返回Web Server。 php-cgi關閉本次串連並等待下次串連。 0x03 PHP中的CGI實現 PHP中的CGI實現了FastCGI協議,是一個TCP或UDP協議的伺服器接受來自Web伺服器的請求,當啟動時建立TCP/UDP協議的伺服器socket監聽,並接受相關請求進行處理。隨後就進入了PHP的生命週期:模組初始化,sapi初始化,處理PHP請求,模組關閉,sapi關閉。以上構成了PHP中CGI的生命週期。 以TCP為例,在TCP的服務端,一般會執行這樣的幾個操作步驟: 1.調用socket函數建立一個TCP用的流式通訊端 2.調用bind函數將伺服器的本地地址與前面建立的通訊端綁定 3.調用listen函數監聽新建立的通訊端,等待用戶端發起的串連請求 4.伺服器處理序調用accept函數進入阻塞狀態,知道有客戶進程調用connect函數建 立串連 5.當串連建立後,伺服器調用read_stream函數讀取用戶端的請求 6.處理完資料後,伺服器調用write函數向用戶端發送應答 <!-------------- 這就是活生生的socket通訊啊 ---------------> 下面從PHP源碼來看這個過程: (以下代碼我只保留了關鍵區段,完整代碼請自行查看PHP源碼) 1.socket的建立、綁定和監聽(在源碼的sapi/cgi/fastcgi.c中) /* Create, bind socket and start listen on it */ if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || #ifdef SO_REUSEADDR setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)) < 0 || #endif bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 || listen(listen_socket, backlog) < 0) { fprintf(stderr, "Cannot bind/listen socket - [%d] %s.\n",errno, strerror(errno)); return -1; } 2.當服務端初始化完成後,進程調用accept函數進入阻塞狀態,在main函數中我們看到如下代碼: while (parent) { do { pid = fork(); // 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)); /* 調用fcgi_accept_request接受請求 */ while (!fastcgi || fcgi_accept_request(&request) >= 0) { SG(server_context) = (void *) &request; init_request_info(TSRMLS_C); CG(interactive) = 0; } } 3.調用read函數讀取用戶端請求: static int fcgi_read_request(fcgi_request *req) { fcgi_header hdr; int len, padding; unsigned char buf[FCGI_MAX_LENGTH+8]; req->keep = 0; req->closed = 0; req->in_len = 0; req->out_hdr = NULL; req->out_pos = req->out_buf; req->has_env = 1; /*調用sage_read讀取fcgi_request類型的資料req*/ if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr.version < FCGI_VERSION_1) { return 0; } } 在請求初始化完成,讀取請求完畢後,就該處理請求的PHP檔案了。 假設此次請求為PHP_MODE_STANDARD則會調用php_execute_script執行PHP檔案。在此函數中它先初始化此檔案相關的一些內容,然後再調用zend_execute_scripts函數,對PHP檔案進行詞法分析和文法分析,產生中間代碼, 並執行zend_execute函數,從而執行這些中間代碼。 4.fastCGI處理完成 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處於開啟狀態(reg->fd >= 0),並且請求未關閉,則會將執行後的結果刷到用戶端,並將請求的關閉設定為真。 將資料刷到用戶端的程式調用的是fcgi_flush函數。在此函數中,關鍵是在於答應頭的構造和寫操作。 程式的寫操作是調用的safe_write函數,而safe_write函數中對於最終的寫操作針對win和linux環境做了區分,在Win32下,如果是TCP串連則用send函數,如果是非TCP則和非win環境一樣使用write函數。如下代碼: static inline ssize_t safe_write(fcgi_request *req, const void *buf, size_t count) { int ret; size_t n = 0; do { errno = 0; #ifdef _WIN32 /*win32環境*/ if (!req->tcp) { /*非TCP串連,調用write函數*/ ret = write(req->fd, ((char*)buf)+n, count-n); } else { /*TCP串連,調用send函數*/ ret = send(req->fd, ((char*)buf)+n, count-n, 0); if (ret <= 0) { errno = WSAGetLastError(); } } #else /*其他環境, 調用write函數*/ ret = write(req->fd, ((char*)buf)+n, count-n); #endif if (ret > 0) { n += ret; } else if (ret <= 0 && errno != 0 && errno != EINTR) { return ret; } } while (n != count); return n; } 以上就是基於TCP串連的PHP FastCGI的實現過程。0x04 參考文獻《深入理解PHP核心》