實戰 FastCGI(轉)三

- 中國WEB開發者網絡 (http://www.webasp.net)
-- 技術教程 (http://www.webasp.net/article/)
--- 實戰 FastCGI(轉)三 (http://www.webasp.net/article/8/7732.htm)
-- 作者:未知
-- 發佈日期: 2003-09-13
3. 撰寫 FastCGI 應用程序

撰寫全新的 FastCGI 應用程序,或是將舊有的 CGI 程序改寫成 FastCGI 應用程序都非常的簡單,只要使用 fcgi-devkit 所附的 fcgi_stdio 函式庫即可。

基本上,fcgi_stdio 函式庫已被設計成讓開發人員撰寫 FastCGI 應用程序就像寫一般 CGI 程序一樣,同時做到程序保有和 CGI 最大的兼容度,又能享受到 FastCGI 所帶來的優點。使用 fcgi_stdio 函式庫的另一項好處是,編譯出來的執行檔可同時以 CGI 以及 FastCGI 的方式執行。

3.1 程序架構
對 CGI 程序而言,其生命期就是從一個聯機請求 (request) 開始到聯機結束。而 FastCGI 程序就像是比較『長命』的 CGI 程序,其生命期橫跨不同的聯機請求,可從 Web 服務器激活開始到 Web 服務器停止。

由於 FastCGI 程序長命的特性,它和一般 CGI 程序主要的差異就在於把初始化 (initialization) 的部份和處理聯機請求的部份區分開來,程序的架構如下所示:

Initialization Code
Start of response loop

body of response loop
End of response loop

Initialization Code 的部份只會在 FastCGI 程序激活時執行一次,程序初始化的部份像是內存的配置,建立和數據庫的聯機等都可以寫在這裡。

而 Start of response loop 到 End of response loop 之間的程序在每次發生聯機請求時就會執行,這部份的程序才是真正處理每次聯機請求要做的事情。例如接受使用者輸入的參數,從數據庫取出資料,執行運算動作,回復結果給使用者等等。

一個簡單的 FastCGI 程序如下 (tiny-fcgi.c)

#include "fcgi_stdio.h"
#include <stdlib.h>
void main(void)
{
/* Initialization Code */
int count = 0;
/* Start of response loop */
while(FCGI_Accept() >= 0) {
/* body of response loop */
printf("Content-type: text/html\r\n"
"\r\n"
"<title>FastCGI Hello! (C, fcgi_stdio library)</title>"
"<h1>FastCGI Hello! (C, fcgi_stdio library)</h1>"
"Request number %d running on host <i>%s</i> "
"Process ID: %d\n",
++count, getenv("SERVER_NAME"), getpid());
}
/* End of response loop */
}

3.2 引入 fcgi_stdio.h 標頭檔
開始撰寫一個新的 FastCGI 應用程序時,必須引入 fcgi_stdio.h 這個標頭檔:

#include ``fcgi_stdio.h''
如果要改寫舊有的 CGI 程序成 FastCGI 程序,請把原本引入 stdio.h 的部份換掉:

#ifndef FASTCGI
#include <stdio.h>
#else
#include ``fcgi_stdio.h''
#endif

上面的寫法是利用 C 的前置編譯器做處理,如果在編譯時加入 -DFASTCGI 的參數則引入 fcgi_stdio.h ,反之則否。假設我們把 fcgi_stdio.h 標頭文件放在 /usr/local/include/fastcgi 這個目錄下的話,為了在編譯時引入 fcgi_stdio.h 標頭檔,請加入 -I/usr/local/include/fastcgi 的參數。

$ cc -I/usr/local/include/fastcgi -c program.c

注意
如果你的程序使用到其它的函式庫,請務必檢查這些函式庫是否有使用到 stdio.h 這個檔,如果有的話必須一併換成 fcgi_stdio.h。舉例來說,假如你的程序用到 GD 函式庫4來產生 GIF 圖形,請記得把 gd.h 中引入 stdio.h 的部份換成 fcgi_stdio.h,否則遇到 IO 的函式時會發生錯誤 (Core Dump)。

3.3 FastCGI 處理循環
在 FastCGI 程序中負責處理聯機請求的程序,必須用一個 while 循環包起來,利用 fcgi_stdio 函式庫中的 FCGI_Accept() 函式庫來判斷是否有新的聯機請求發生。

FastCGI 程序被激活後,首先進行初始化的動作,如變量宣告、內存配置、或是與數據庫建立聯機等。執行到 FCGI_Accept() 時程序就會暫停,等到當某一個聯機請求發生時,FCGI_Accept() 會傳回大於零的值,程序即刻進入循環的主體,可能是執行 SQL 指令,運算以及產生 HTML 的輸出。處理完後又回到 FCGI_Accept() 循環的開始,等待下一個聯機請求。

由此可見, FastCGI 激活之後,省去原本 CGI 程序中 fork,內存配置,建立數據庫聯機等步驟,處理每個聯機請求的時間幾乎等於 FCGI_Accept() 這個循環中運算所需的時間。如果妥善將每次聯機請求時一樣的程序代碼從 FCGI_Accept() 循環抽出來,只保留每次會產生不同結果的部份,則程序處理每次聯機請求的時間可以更短,整個網站的效率也可以大幅的提升。

瞭解 FastCGI 程序的基本架構後,可以發現一般的 CGI 程序,只要加入 FCGI_Accept() 這個 while 循環後,大概就可以變成 FastCGI 的程序了。

3.4 煉結 libfcgi.a 函式庫
FastCGI 程序要煉結到 libfcgi.a 函式庫才可正確產生出執行檔,假設 libfcgi.a 的位置在 /usr/local/lib 這個目錄下,我們在編譯 (煉結時) 要加入 -L/usr/local/lib -lfcgi 的參數。


$ cc -o program.fcg program.o -L/usr/local/lib -lfcgi

3.5 撰寫 FastCGI 程序的注意事項
由於 FastCGI 程序被激活後就常駐在內存之中,對每個 FastCGI 的執行行程 (running process) 而言,每次聯機請求共享的變量空間是相同的,因些選寫 FastCGI 程序要特別留意一些注意事項。

1. 記住,對每個 FastCGI 程序而言,只有 FCGI_Accept() 循環內的程序才是真正處理每次聯機請求的主體,假設在程序中要讀取和 CGI 有關的環境變量,或是使用者透過窗體 (form) 所輸入的資料,必須在 FCGI_Accept() 循環內才處理。
2. 對每次聯機請求會改變的變量,別忘了在處理新的聯機請求前把變量初始化 (除非是有特殊用途) ,以避免變量值在不同的聯機請求之間互相影響。
3. 在 FCGI_Accept() 循環中配置 (allocate) 的內存別忘了釋放 (free)。在一般的 CGI 程序中, Memory Leak 不算是太大的問題,因為每處理完一次聯機請求,所有用到的內存隨著 CGI 程序的結束也被釋放。但是 FastCGI 程序會一直常駐在內存中,如果在 FCGI_Accept() 中有配置額外的內存,在循環結束前沒有釋放掉,則每處理一次聯機請求就吃掉一些系統的內存,多跑幾次下來,系統資源大概就被耗光了。Memory leak 的問題是 FastCGI 最常見的錯誤,要特別小心處理。
4. FastCGI 程序和其所要引用的子程序中,用到 stdio.h 函式的部份,記得要改成 fcgi_stdio.h 。否則當程序在遇到 IO 的動作時就會發生 Core Dump 的情況。
5. 在 FCGI_Accept() 循環中使用 FCGI_Finish() 函式以取代 exit() 函式。原本 CGI 程序中發生錯誤時,可能直接呼叫 exit() 函式結束 CGI 行程,FastCGI 程序也可以如此,因為 mod_fastcgi 模塊在 FastCGI 程序發生錯誤而意外結束時,會自動再激活另一個 FastCGI 的行程。但比較好的作法是呼叫 fcgi_stdio.h 中的 FCGI_Finish() 函式,呼叫 FCGI_Finish() 函式會跳出目前程序正在運算中的循環,回到 FCGI_Accept() 等待下一個聯機請求。如此可以省去一些網站服務器激活新的 FastCGI 行程的負擔。

webasp.net