CGI(Common Gateway Interface)公共网关接口,是HTTP服务器与其他程序通信的工具。FastCGI是一个long-live型的CGI,支持分布式计算,它将CGI解释器进程保持在内存中并因此获得较高的性能。

FastCGI工作方式是接受Web服务器的请求,以HTTP Request的方式进行响应,实现了应用程序与Web服务器的分离。它的使用需要在Web服务器中安装支持组件,目前支持Apache、Nginx、IIS、Lighttpd等。


优点有:

  • 稳定性,fastcgi是以独立的进程池运行来cgi,单独一个进程死掉,系统可以很轻易的丢弃,然后重新分配新的进程来运行逻辑
  • 安全性,fastcgi和宿主的server完全独立,fastcgi怎么down也不会把server搞垮
  • 性能, fastcgi把动态逻辑的处理从server中分离出来, 大负荷的IO处理还是留给宿主server, 这样宿主server可以一心一意作IO,对于一个普通的动态网页来说,,逻辑处理可能只有一小部分,大量的图片等静态IO处理完全不需要逻辑程序的参与
  • 扩展性,fastcgi是一个中立的技术标准,完全可以支持任何语言写的处理程序(php、java、python、C++)

FastCGI编程包括四部分:初始化编码、接收请求循环、响应内容、响应结束循环。

  1. FCGX_Request request;  
  2. FCGX_Init();  
  3. int sock_fd = FCGX_OpenSocket("10.3.17.75:8003",100);  
  4. FCGX_InitRequest(&request, sock_fd, 0);  
  5. while (FCGX_Accept_r(&request) >= 0) {  
  6.   //get param 1  
  7.   map<string,string> param_map;  
  8.   for(int i = 0; request.envp[i]; ++i) {  
  9.     string s = request.envp[i];  
  10.     size_t pos = s.find_first_of('=');  
  11.     if (pos > 0 && pos < s.size() - 1) {  
  12.       param_map.insert(make_pair(s.substr(0,pos), s.substr(pos+1)));  
  13.     }  
  14.   }  
  15.   //or 2   
  16.   char * clenstr = FCGX_GetParam("CONTENT_LENGTH", request.envp);  
  17.   //do something  
  18.   FCGX_Stream* fcgi_out = request.out;  
  19.   string output="test";  
  20.   FCGX_PutS(output.c_str(), fcgi_out);  
  21.   //finish  
  22.   FCGX_Finish_r(&request);  




0.背景

在项目中加入了等待通讯的内容,所以原来单个请求处理时间增加了。单线程处理的fcgi就会浪费CPU和用户时间,所以需要多线程来处理,减少用户排队时间。

将处理用户请求的部分从单线程变为多线程,需要大概了解改动会不会影响性能。

得到的结论是:多线程和单线程在执行的流程和使用方法几乎一样,所以多线程不会带来额外的负担。

1.单线程的处理步骤

1.1一个简单的单线程fcgi请求
C代码   收藏代码
  1. #include <fcgi_stdio.h>  
  2.   
  3. void main(void)  
  4. {  
  5.     int count = 0;  
  6.     while(FCGI_Accept() >= 0) {  
  7.         printf("Content-type: text/html\r\n");  
  8.         printf("\r\n");  
  9.         printf("Hello world!<br>\r\n");  
  10.         printf("Request number %d.", count++);  
  11.     }  
  12.     exit(0);  
  13. }  
1.2进入FCGI_Accept。

进入这个 FCGI_Accept() 方法里面,在文件fcgi_stdio.c里。

C代码   收藏代码
  1. int FCGI_Accept(void)  
  2. {  
  3.     //变量表示是否接收请求。默认为Fasle,不接收请求  
  4.     if(!acceptCalled) {  
  5.         //判断是否为cgi,变量为全局静态的,下次还会用。  
  6.         isCGI = FCGX_IsCGI();  
  7.         //状态改为接收请求。  
  8.         acceptCalled = TRUE;  
  9.         //请求的收尾,将数值清空赋初值。  
  10.         atexit(&FCGI_Finish);  
  11.     } else if(isCGI) {  
  12.         //不是第一次请求,并且是cgi程序。  
  13.         return(EOF);  
  14.     }  
  15.     if(isCGI) {  
  16.         //cgi的初始赋值操作,不关心。  
  17.         ...  
  18.     } else {  
  19.         FCGX_Stream *in, *out, *error;  
  20.         //char** 字符串数组。  
  21.         FCGX_ParamArray envp;  
  22.         //接受请求,这个方法下面介绍  
  23.         int acceptResult = FCGX_Accept(&in, &out, &error, &envp);  
  24.         //接收失败,返回<0,这也是为什么在循环上判断是 while(FCGI_Accept() >= 0)  
  25.         if(acceptResult < 0) {  
  26.             return acceptResult;  
  27.         }  
  28.         //将得到的数据赋值给对应的输出,输入,data。  
  29.         FCGI_stdin->stdio_stream = NULL;  
  30.         FCGI_stdin->fcgx_stream = in;  
  31.         FCGI_stdout->stdio_stream = NULL;  
  32.         FCGI_stdout->fcgx_stream = out;  
  33.         FCGI_stderr->stdio_stream = NULL;  
  34.         FCGI_stderr->fcgx_stream = error;  
  35.         environ = envp;  
  36.     }  
  37.     //结束  
  38.     return 0;  
  39. }  
1.3  FCGX_Accept(&in, &out, &error, &envp)

等待接收请求的方法,在fcgiaoo.c里。

C代码   收藏代码
  1. static FCGX_Request the_request;  
  2.   
  3. int FCGX_Accept(FCGX_Stream **in,FCGX_Stream **out,FCGX_Stream **err,FCGX_ParamArray *envp)  
  4. {  
  5.     int rc;//定义返回的变量。  
  6.     //是否初始化过。  
  7.     if (! libInitialized) {  
  8.         rc = FCGX_Init();  
  9.         if (rc) {  
  10.             return rc;  
  11.         }  
  12.     }  
  13.     //接收数据,下面介绍  
  14.     rc = FCGX_Accept_r(&the_request);  
  15.     //给对应流和数据赋值。  
  16.     *in = the_request.in;  
  17.     *out = the_request.out;  
  18.     *err = the_request.err;  
  19.     *envp = the_request.envp;  
  20.   
  21.     return rc;  
  22. }  
1.4 FCGX_Accept_r(),同在fcgiapp.c里面;
C代码   收藏代码
  1. /* 
  2.  *---------------------------------------------------------------------- 
  3.  * 
  4.  * FCGX_Accept_r -- 
  5.  * 
  6.  *      从http server 接收一个新的请求 
  7.  * Results: 
  8.  *      正确返回0,错误返回-1. 
  9.  * Side effects: 
  10.  * 
  11.  *      通过FCGX_Accept完成请求的接收,创建input,output等流并且各自分配给in 
  12.  *      ,out,err.创建参数数据从FCGX_GetParam中取出,并且给envp。 
  13.  *      不要保存指针和字符串,他们会在下次请求中的FcGX_Finish中被释放。 
  14.  *---------------------------------------------------------------------- 
  15.  */  
  16. int FCGX_Accept_r(FCGX_Request *reqDataPtr)  
  17. {  
  18.     if (!libInitialized) {  
  19.         return -9998;  
  20.     }  
  21.   
  22.     //将当前的reqData完成请求。将内容释放,初始化。  
  23.     FCGX_Finish_r(reqDataPtr);  
  24.     //while  
  25.     for (;;) {  
  26.         //ipcFd 是双方通讯的管道,在上面的FCGX_Finish_r中被赋值-1。  
  27.         if (reqDataPtr->ipcFd < 0) {  
  28.             int fail_on_intr = reqDataPtr->flags & FCGI_FAIL_ACCEPT_ON_INTR;  
  29.             //接收一次请求,传入socket,没有请求会在这里等待。这里面是重点,可是我看不懂。  
  30.             reqDataPtr->ipcFd = OS_Accept(reqDataPtr->listen_sock, fail_on_intr, webServerAddressList);  
  31.             if (reqDataPtr->ipcFd < 0) {  
  32.                 return (errno > 0) ? (0 - errno) : -9999;  
  33.             }  
  34.         }  
  35.         reqDataPtr->isBeginProcessed = FALSE;  
  36.         reqDataPtr->in = NewReader(reqDataPtr, 8192, 0);  
  37.         FillBuffProc(reqDataPtr->in);  
  38.         if(!reqDataPtr->isBeginProcessed) {  
  39.             goto TryAgain;  
  40.         }  
  41.         {  
  42.             //查看请求类型。  
  43.             char *roleStr;  
  44.             switch(reqDataPtr->role) {  
  45.                 case FCGI_RESPONDER:  
  46.                     roleStr = "FCGI_ROLE=RESPONDER";  
  47.                     break;  
  48.                 case FCGI_AUTHORIZER:  
  49.                     roleStr = "FCGI_ROLE=AUTHORIZER";  
  50.                     break;  
  51.                 case FCGI_FILTER:  
  52.                     roleStr = "FCGI_ROLE=FILTER";  
  53.                     break;  
  54.                 default:  
  55.                     goto TryAgain;  
  56.             }  
  57.             //创建存储参数QueryString的空间。  
  58.             reqDataPtr->paramsPtr = NewParams(30);  
  59.             //将请求类型当做key-value加入参数里。  
  60.             PutParam(reqDataPtr->paramsPtr, StringCopy(roleStr));  
  61.         }  
  62.         //将输入流以制定的方式读取(在哪停止,是否需要跳过请求)  
  63.         SetReaderType(reqDataPtr->in, FCGI_PARAMS);  
  64.         //将参数读取写入key=value。  
  65.         if(ReadParams(reqDataPtr->paramsPtr, reqDataPtr->in) >= 0) {  
  66.             //跳出循环,否则等下一次请求。  
  67.             break;  
  68.         }  
  69.   
  70.         //释放这些中间产生的东西。将ipcFd置为-1.  
  71. TryAgain:  
  72.         FCGX_Free(reqDataPtr, 1);  
  73.   
  74.     } /* for (;;) */  
  75.     //将剩下的信息赋值。完成一个请求的开始部分。  
  76.     SetReaderType(reqDataPtr->in, FCGI_STDIN);  
  77.     reqDataPtr->out = NewWriter(reqDataPtr, 8192, FCGI_STDOUT);  
  78.     reqDataPtr->err = NewWriter(reqDataPtr, 512, FCGI_STDERR);  
  79.     reqDataPtr->nWriters = 2;  
  80.     reqDataPtr->envp = reqDataPtr->paramsPtr->vec;  
  81.     return 0;  
  82. }  
1.5 在接收请求之前执行的FCGX_Finish_r(reqDataPtr),在fcgiapp.c里;
C代码   收藏代码
  1. void FCGX_Finish_r(FCGX_Request *reqDataPtr)  
  2. {  
  3.     int close;  
  4.   
  5.     if (reqDataPtr == NULL) {  
  6.         return;  
  7.     }  
  8.   
  9.     close = !reqDataPtr->keepConnection;  
  10.   
  11.     if (reqDataPtr->in) {  
  12.         close |= FCGX_FClose(reqDataPtr->err);  
  13.         close |= FCGX_FClose(reqDataPtr->out);  
  14.     close |= FCGX_GetError(reqDataPtr->in);  
  15.     }  
  16.   
  17.     FCGX_Free(reqDataPtr, close);  
  18. }  

 

基本结束了,只能看懂流程。

二 多线程的请求

2.1多线程请求例子

官网多线程的例子,(http://www.fastcgi.com/devkit/examples/threaded.c)

去掉多余的输出。

 

C代码   收藏代码
  1. #define THREAD_COUNT 20  
  2.   
  3. static int counts[THREAD_COUNT];  
  4.   
  5. static void *doit(void *a)  
  6. {  
  7.     int rc;  
  8.     FCGX_Request request;  
  9.     FCGX_InitRequest(&request, 0, 0);  
  10.     for (;;)  
  11.     {  
  12.         rc = FCGX_Accept_r(&request);  
  13.   
  14.         if (rc < 0)  
  15.             break;  
  16.         FCGX_FPrintF(request.out,  
  17.             "Content-type: text/html\r\n"  
  18.             "\r\n"  
  19.             "<title>FastCGI Hello! ");  
  20.   
  21.         sleep(2);  
  22.         FCGX_Finish_r(&request);  
  23.     }  
  24.     return NULL;  
  25. }  
  26.   
  27. int main(void)  
  28. {  
  29.     int i;  
  30.     pthread_t id[THREAD_COUNT];  
  31.   
  32.     FCGX_Init();  
  33.   
  34.     for (i = 1; i < THREAD_COUNT; i++)  
  35.         pthread_create(&id[i], NULL, doit, (void*)i);  
  36.   
  37.     doit(0);  
  38.     return 0;  
  39. }  
2.2和fcgi有关的方法

在main方法里

C代码   收藏代码
  1. FCGX_Init();  
  2. Initilize the FCGX library.  This is called by FCGX_Accept()  
  3. but must be called by the user when using FCGX_Accept_r().  

在多线程里:

C代码   收藏代码
  1. FCGX_Request request;  
  2. FCGX_InitRequest(&request, 0, 0);  
  3. while(){  
  4. rc = FCGX_Accept_r(&request);  
  5. FCGX_FPrintF(request.out,"");  
  6. FCGX_Finish_r(&request);  
  7. }  

3.结束

刚刚开始用C语言,希望说错的地方大家提出来。


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐