0.背景
在項目中加入了等待通訊的内容,是以原來單個請求處理時間增加了。單線程處理的fcgi就會浪費CPU和使用者時間,是以需要多線程來處理,減少使用者排隊時間。
将處理使用者請求的部分從單線程變為多線程,需要大概了解改動會不會影響性能。
得到的結論是:多線程和單線程在執行的流程和使用方法幾乎一樣,是以多線程不會帶來額外的負擔。
1.單線程的處理步驟
1.1一個簡單的單線程fcgi請求
#include <fcgi_stdio.h>
void main(void)
{
int count = 0;
while(FCGI_Accept() >= 0) {
printf("Content-type: text/html\r\n");
printf("\r\n");
printf("Hello world!<br>\r\n");
printf("Request number %d.", count++);
}
exit(0);
}
1.2進入FCGI_Accept。
進入這個 FCGI_Accept() 方法裡面,在檔案fcgi_stdio.c裡。
int FCGI_Accept(void)
{
//變量表示是否接收請求。預設為Fasle,不接收請求
if(!acceptCalled) {
//判斷是否為cgi,變量為全局靜态的,下次還會用。
isCGI = FCGX_IsCGI();
//狀态改為接收請求。
acceptCalled = TRUE;
//請求的收尾,将數值清空賦初值。
atexit(&FCGI_Finish);
} else if(isCGI) {
//不是第一次請求,并且是cgi程式。
return(EOF);
}
if(isCGI) {
//cgi的初始指派操作,不關心。
...
} else {
FCGX_Stream *in, *out, *error;
//char** 字元串數組。
FCGX_ParamArray envp;
//接受請求,這個方法下面介紹
int acceptResult = FCGX_Accept(&in, &out, &error, &envp);
//接收失敗,傳回<0,這也是為什麼在循環上判斷是 while(FCGI_Accept() >= 0)
if(acceptResult < 0) {
return acceptResult;
}
//将得到的資料指派給對應的輸出,輸入,data。
FCGI_stdin->stdio_stream = NULL;
FCGI_stdin->fcgx_stream = in;
FCGI_stdout->stdio_stream = NULL;
FCGI_stdout->fcgx_stream = out;
FCGI_stderr->stdio_stream = NULL;
FCGI_stderr->fcgx_stream = error;
environ = envp;
}
//結束
return 0;
}
1.3 FCGX_Accept (&in, &out, &error, &envp)
等待接收請求的方法,在fcgiaoo.c裡。
static FCGX_Request the_request;
int FCGX_Accept(FCGX_Stream **in,FCGX_Stream **out,FCGX_Stream **err,FCGX_ParamArray *envp)
{
int rc;//定義傳回的變量。
//是否初始化過。
if (! libInitialized) {
rc = FCGX_Init();
if (rc) {
return rc;
}
}
//接收資料,下面介紹
rc = FCGX_Accept_r(&the_request);
//給對應流和資料指派。
*in = the_request.in;
*out = the_request.out;
*err = the_request.err;
*envp = the_request.envp;
return rc;
}
1.4 FCGX_Accept_r (),同在fcgiapp.c裡面;
/*
*----------------------------------------------------------------------
*
* FCGX_Accept_r --
*
* 從http server 接收一個新的請求
* Results:
* 正确傳回0,錯誤傳回-1.
* Side effects:
*
* 通過FCGX_Accept完成請求的接收,建立input,output等流并且各自配置設定給in
* ,out,err.建立參數資料從FCGX_GetParam中取出,并且給envp。
* 不要儲存指針和字元串,他們會在下次請求中的FcGX_Finish中被釋放。
*----------------------------------------------------------------------
*/
int FCGX_Accept_r(FCGX_Request *reqDataPtr)
{
if (!libInitialized) {
return -9998;
}
//将目前的reqData完成請求。将内容釋放,初始化。
FCGX_Finish_r(reqDataPtr);
//while
for (;;) {
//ipcFd 是雙方通訊的管道,在上面的FCGX_Finish_r中被指派-1。
if (reqDataPtr->ipcFd < 0) {
int fail_on_intr = reqDataPtr->flags & FCGI_FAIL_ACCEPT_ON_INTR;
//接收一次請求,傳入socket,沒有請求會在這裡等待。這裡面是重點,可是我看不懂。
reqDataPtr->ipcFd = OS_Accept(reqDataPtr->listen_sock, fail_on_intr, webServerAddressList);
if (reqDataPtr->ipcFd < 0) {
return (errno > 0) ? (0 - errno) : -9999;
}
}
reqDataPtr->isBeginProcessed = FALSE;
reqDataPtr->in = NewReader(reqDataPtr, 8192, 0);
FillBuffProc(reqDataPtr->in);
if(!reqDataPtr->isBeginProcessed) {
goto TryAgain;
}
{
//檢視請求類型。
char *roleStr;
switch(reqDataPtr->role) {
case FCGI_RESPONDER:
roleStr = "FCGI_ROLE=RESPONDER";
break;
case FCGI_AUTHORIZER:
roleStr = "FCGI_ROLE=AUTHORIZER";
break;
case FCGI_FILTER:
roleStr = "FCGI_ROLE=FILTER";
break;
default:
goto TryAgain;
}
//建立存儲參數QueryString的空間。
reqDataPtr->paramsPtr = NewParams(30);
//将請求類型當做key-value加入參數裡。
PutParam(reqDataPtr->paramsPtr, StringCopy(roleStr));
}
//将輸入流以制定的方式讀取(在哪停止,是否需要跳過請求)
SetReaderType(reqDataPtr->in, FCGI_PARAMS);
//将參數讀取寫入key=value。
if(ReadParams(reqDataPtr->paramsPtr, reqDataPtr->in) >= 0) {
//跳出循環,否則等下一次請求。
break;
}
//釋放這些中間産生的東西。将ipcFd置為-1.
TryAgain:
FCGX_Free(reqDataPtr, 1);
} /* for (;;) */
//将剩下的資訊指派。完成一個請求的開始部分。
SetReaderType(reqDataPtr->in, FCGI_STDIN);
reqDataPtr->out = NewWriter(reqDataPtr, 8192, FCGI_STDOUT);
reqDataPtr->err = NewWriter(reqDataPtr, 512, FCGI_STDERR);
reqDataPtr->nWriters = 2;
reqDataPtr->envp = reqDataPtr->paramsPtr->vec;
return 0;
}
1.5 在接收請求之前執行的FCGX_Finish_r (reqDataPtr),在fcgiapp.c裡;
void FCGX_Finish_r(FCGX_Request *reqDataPtr)
{
int close;
if (reqDataPtr == NULL) {
return;
}
close = !reqDataPtr->keepConnection;
if (reqDataPtr->in) {
close |= FCGX_FClose(reqDataPtr->err);
close |= FCGX_FClose(reqDataPtr->out);
close |= FCGX_GetError(reqDataPtr->in);
}
FCGX_Free(reqDataPtr, close);
}
基本結束了,隻能看懂流程。
二 多線程的請求
2.1多線程請求例子
官網多線程的例子,(http://www.fastcgi.com/devkit/examples/threaded.c)
去掉多餘的輸出。
#define THREAD_COUNT 20
static int counts[THREAD_COUNT];
static void *doit(void *a)
{
int rc;
FCGX_Request request;
FCGX_InitRequest(&request, 0, 0);
for (;;)
{
rc = FCGX_Accept_r(&request);
if (rc < 0)
break;
FCGX_FPrintF(request.out,
"Content-type: text/html\r\n"
"\r\n"
"<title>FastCGI Hello! ");
sleep(2);
FCGX_Finish_r(&request);
}
return NULL;
}
int main(void)
{
int i;
pthread_t id[THREAD_COUNT];
FCGX_Init();
for (i = 1; i < THREAD_COUNT; i++)
pthread_create(&id[i], NULL, doit, (void*)i);
doit(0);
return 0;
}
2.2和fcgi有關的方法
在main方法裡
FCGX_Init();
Initilize the FCGX library. This is called by FCGX_Accept()
but must be called by the user when using FCGX_Accept_r().
在多線程裡:
FCGX_Request request;
FCGX_InitRequest(&request, 0, 0);
while(){
rc = FCGX_Accept_r(&request);
FCGX_FPrintF(request.out,"");
FCGX_Finish_r(&request);
}
3.結束
剛剛開始用C語言,希望說錯的地方大家提出來。