|=-----------------------------------------------------------------------=||=------------------------=[ 深入理解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將CGIhtml' target='_blank'>環境變量和標準輸入發送到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.",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內核》PHP編程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答