|=-----------------------------------------------------------------------=||=------------------------=[ 深入理解FastCGI ]=--------------------------=||=-----------------------------------------------------------------------=||=--------------------------=[ by d4shman ]=---------------------------=||=-----------------------------------------------------------------------=||=-------------------------=[ May 7, 2014 ]=---------------------------=||=-----------------------------------------------------------------------=|[目錄](méi)0x01 什么是FastCGI0x02 FastCGI的工作流程0x03 PHP中的CGI實(shí)現(xiàn)0x04 參考文獻(xiàn)0x01 什么是FastCGI CGI全稱(chēng)是“通用網(wǎng)關(guān)接口”( Common Gateway Interface),它可以讓一個(gè)客戶(hù)端從網(wǎng)頁(yè)瀏覽器向執(zhí)行在web服務(wù)器上的程序請(qǐng)求數(shù)據(jù)。CGI描述了客戶(hù)端和這個(gè)程序之間傳遞數(shù)據(jù)的一種標(biāo)準(zhǔn)。 FastCGI是web服務(wù)器和處理程序之間通訊的一種協(xié)議, 是CGI的一種改進(jìn)方案, FastCGI像是一個(gè)常駐(long live)型的CGI, 它可以一直執(zhí)行,在請(qǐng)求到達(dá)時(shí)不會(huì)花費(fèi)時(shí)間去fork一個(gè)進(jìn)程來(lái)處理(這是CGI最為人詬病的fork-and-execute模式)。 CGI程序反復(fù)加載是CGI性能低下的主要原因,F(xiàn)astCGI將CGI解釋器進(jìn)程保持在內(nèi)存內(nèi)中,以此獲得較高的性能。同時(shí),F(xiàn)astCGI還支持分布式計(jì)算,也就是說(shuō),Web Server和FastCGI可以部署在不同的服務(wù)器上。0x02 FastCGI的工作流程 1.Web server啟動(dòng)時(shí)載入FastCGI進(jìn)程管理器(Apache Module、IIS ISAPI等) 2.FastCGI進(jìn)程管理器自身初始化,啟動(dòng)多個(gè)CGI解釋器進(jìn)程php-cgi并等待來(lái)自 Web Server的連接。 3.當(dāng)客戶(hù)端的請(qǐng)求到達(dá)Web Server時(shí),F(xiàn)astCGI選擇并連接到一個(gè)CGI解釋器。 Web server將CGIhtml' target='_blank'>環(huán)境變量和標(biāo)準(zhǔn)輸入發(fā)送到FastCGI子進(jìn)程php-cgi。 4.FastCGI子進(jìn)程完成處理后將標(biāo)準(zhǔn)輸出和錯(cuò)誤信息從同一連接返回Web Server。 php-cgi關(guān)閉本次連接并等待下次連接。 0x03 PHP中的CGI實(shí)現(xiàn) PHP中的CGI實(shí)現(xiàn)了FastCGI協(xié)議,是一個(gè)TCP或UDP協(xié)議的服務(wù)器接受來(lái)自Web服務(wù)器的請(qǐng)求,當(dāng)啟動(dòng)時(shí)創(chuàng)建TCP/UDP協(xié)議的服務(wù)器socket監(jiān)聽(tīng),并接受相關(guān)請(qǐng)求進(jìn)行處理。隨后就進(jìn)入了PHP的生命周期:模塊初始化,sapi初始化,處理PHP請(qǐng)求,模塊關(guān)閉,sapi關(guān)閉。以上構(gòu)成了PHP中CGI的生命周期。 以TCP為例,在TCP的服務(wù)端,一般會(huì)執(zhí)行這樣的幾個(gè)操作步驟: 1.調(diào)用socket函數(shù)創(chuàng)建一個(gè)TCP用的流式套接字 2.調(diào)用bind函數(shù)將服務(wù)器的本地地址與前面創(chuàng)建的套接字綁定 3.調(diào)用listen函數(shù)監(jiān)聽(tīng)新創(chuàng)建的套接字,等待客戶(hù)端發(fā)起的連接請(qǐng)求 4.服務(wù)器進(jìn)程調(diào)用accept函數(shù)進(jìn)入阻塞狀態(tài),知道有客戶(hù)進(jìn)程調(diào)用connect函數(shù)建 立連接 5.當(dāng)連接建立后,服務(wù)器調(diào)用read_stream函數(shù)讀取客戶(hù)端的請(qǐng)求 6.處理完數(shù)據(jù)后,服務(wù)器調(diào)用write函數(shù)向客戶(hù)端發(fā)送應(yīng)答 <!-------------- 這就是活生生的socket通信啊 ---------------> 下面從PHP源碼來(lái)看這個(gè)過(guò)程: (以下代碼我只保留了關(guān)鍵部分,完整代碼請(qǐng)自行查看PHP源碼) 1.socket的創(chuàng)建、綁定和監(jiān)聽(tīng)(在源碼的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.當(dāng)服務(wù)端初始化完成后,進(jìn)程調(diào)用accept函數(shù)進(jìn)入阻塞狀態(tài),在main函數(shù)中我們看到如下代碼: while (parent) { do { pid = fork(); // fork出新的子進(jìn)程 switch (pid) { case 0: parent = 0; /* don't catch our signals */ sigaction(SIGTERM, &old_term, 0); // 終止信號(hào) sigaction(SIGQUIT, &old_quit, 0); // 終端退出符 sigaction(SIGINT, &old_int, 0); // 終端中斷符 break; ... default: /* Fine */ running++; break; } while (parent && (running < children)); /* 調(diào)用fcgi_accept_request接受請(qǐng)求 */ while (!fastcgi || fcgi_accept_request(&request) >= 0) { SG(server_context) = (void *) &request; init_request_info(TSRMLS_C); CG(interactive) = 0; } } 3.調(diào)用read函數(shù)讀取客戶(hù)端請(qǐng)求: 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; /*調(diào)用sage_read讀取fcgi_request類(lèi)型的數(shù)據(jù)req*/ if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr.version < FCGI_VERSION_1) { return 0; } } 在請(qǐng)求初始化完成,讀取請(qǐng)求完畢后,就該處理請(qǐng)求的PHP文件了。 假設(shè)此次請(qǐng)求為PHP_MODE_STANDARD則會(huì)調(diào)用php_execute_script執(zhí)行PHP文件。在此函數(shù)中它先初始化此文件相關(guān)的一些內(nèi)容,然后再調(diào)用zend_execute_scripts函數(shù),對(duì)PHP文件進(jìn)行詞法分析和語(yǔ)法分析,生成中間代碼, 并執(zhí)行zend_execute函數(shù),從而執(zhí)行這些中間代碼。 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; } 如上,當(dāng)socket處于打開(kāi)狀態(tài)(reg->fd >= 0),并且請(qǐng)求未關(guān)閉,則會(huì)將執(zhí)行后的結(jié)果刷到客戶(hù)端,并將請(qǐng)求的關(guān)閉設(shè)置為真。 將數(shù)據(jù)刷到客戶(hù)端的程序調(diào)用的是fcgi_flush函數(shù)。在此函數(shù)中,關(guān)鍵是在于答應(yīng)頭的構(gòu)造和寫(xiě)操作。 程序的寫(xiě)操作是調(diào)用的safe_write函數(shù),而safe_write函數(shù)中對(duì)于最終的寫(xiě)操作針對(duì)win和linux環(huán)境做了區(qū)分,在Win32下,如果是TCP連接則用send函數(shù),如果是非TCP則和非win環(huán)境一樣使用write函數(shù)。如下代碼: 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環(huán)境*/ if (!req->tcp) { /*非TCP連接,調(diào)用write函數(shù)*/ ret = write(req->fd, ((char*)buf)+n, count-n); } else { /*TCP連接,調(diào)用send函數(shù)*/ ret = send(req->fd, ((char*)buf)+n, count-n, 0); if (ret <= 0) { errno = WSAGetLastError(); } } #else /*其他環(huán)境, 調(diào)用write函數(shù)*/ 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的實(shí)現(xiàn)過(guò)程。 0x04 參考文獻(xiàn)《深入理解PHP內(nèi)核》PHP編程
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注