应工作需求,最近用fastcgi写了个小项目。鉴于网上资料缺乏且存在一些错误,为了少走一些弯路,本人干脆利用课余时间把fastcgi源码读了一遍。总体来讲,整个源码异常的简洁明了,但是fastcgi却有如此稳定的性能表现,不得不让人惊叹。
1.fastcgi简介
CGI全称是“通用网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序一般运行在网络服务器上。 CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。
FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行并且接受来自其它网站服务器来的请求。
FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等。
2.工作原理
1)Web Server启动时载入FastCGI进程管理器。
2)FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web Server的连接。
3)当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。
4)FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。
3.fastcgi单线程与多线程编程
单线程模板:
#include
void main(void)
{
int count = 0;
while(FCGI_Accept() >= 0) {
printf("Content-type: text/html\r\n");
printf("\r\n");
printf("Hello world!
\r\n");
printf("Request number %d.", count++);
}
exit(0);
}
多线程模板:
#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"
"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;
}
3.接口说明
1)重要的数据结构
FCGX_Stream:fastcgi自定义的流。里面包含了读写缓冲区的指针,和相应的操作方法。他们本质上都是对链接过来的套接字fd做操作。这个数据封装最值得借鉴的地方就是体现出了面向对象的编程思想,大大提高了程序的可扩展性和可复用性。
typedef struct FCGX_Stream {
unsigned char *rdNext; /* reader: first valid byte
* writer: equals stop */
unsigned char *wrNext; /* writer: first free byte
* reader: equals stop */
unsigned char *stop; /* reader: last valid byte + 1
* writer: last free byte + 1 */
unsigned char *stopUnget; /* reader: first byte of current buffer
* fragment, for ungetc
* writer: undefined */
int isReader;
int isClosed;
int wasFCloseCalled;
int FCGI_errno; /* error status */
void (*fillBuffProc) (struct FCGX_Stream *stream);
void (*emptyBuffProc) (struct FCGX_Stream *stream, int doClose);
void *data;
} FCGX_Stream;
FCGX_Request:当程序调用FCGX_Accept_r函数时,每次接受到的请求数据都会填充至FCGX_Request类型的结构体当中。
几个重要的成员变量:
in:输入流
out:输出流
err:本质上也是个输出流,他会把一些错误信息返回给链接的客户端。
envp:主要存放cgi的一些环境变量。
ipcFd:链接过来的套接字的fd。
typedef struct FCGX_Request {
int requestId; /* valid if isBeginProcessed */
int role;
FCGX_Stream *in;
FCGX_Stream *out;
FCGX_Stream *err;
char **envp;
/* Don't use anything below here */
struct Params *paramsPtr;
int ipcFd; /* < 0 means no connection */
int isBeginProcessed; /* FCGI_BEGIN_REQUEST seen */
int keepConnection; /* don't close ipcFd at end of request */
int appStatus;
int nWriters; /* number of open writers (0..2) */
int flags;
int listen_sock;
} FCGX_Request;
2)头文件
常用的头文件有两个:
Fcgi_stdio.h:这个头文件里面的接口大多只是对标准C中提供的一些接口进行简单的封装,甚至只是重新宏定义了一下。这类接口以FCGI_开头。
Fcgiapp.h:这个头文件里面实现的接口,完全是具备fastcgi特性的开发接口了。这类接口以FCGX_开头。
3)fastcgiapp文件下接口说明
FCGX_Init:主要是做一些初始化工作。
int FCGX_Init(void)
{
char *p;
if (libInitialized) {
return 0;
}
FCGX_InitRequest(&the_request, FCGI_LISTENSOCK_FILENO, 0);
if (OS_LibInit(NULL) == -1) {
return OS_Errno ? OS_Errno : -9997;
}
p = getenv("FCGI_WEB_SERVER_ADDRS");
webServerAddressList = p ? StringCopy(p) : NULL;
libInitialized = 1;
return 0;
}
FCGX_InitRequest:初始化请求包
int FCGX_InitRequest(FCGX_Request *request, int sock, int flags)
{
memset(request, 0, sizeof(FCGX_Request));
/* Should check that sock is open and listening */
request->listen_sock = sock;
/* Should validate against "known" flags */
request->flags = flags;
request->ipcFd = -1;
return 0;
}
FCGX_Accept_r:这个函数可以说是fastcgi编程的核心函数了,它首先会接收链接请求,然后填充request结构体。
具体代码分析如下:
int FCGX_Accept_r(FCGX_Request *reqDataPtr)
{
if (!libInitialized) {
return -9998;
}
/* Finish the current request, if any. */
FCGX_Finish_r(reqDataPtr);
for (;;) {
/*
* If a connection isn't open, accept a new connection (blocking).
* If an OS error occurs in accepting the connection,
* return -1 to the caller, who should exit.
*/
if (reqDataPtr->ipcFd < 0) {
int fail_on_intr = reqDataPtr->flags & FCGI_FAIL_ACCEPT_ON_INTR;
/*调用accept函数接收链接*/
reqDataPtr->ipcFd = OS_Accept(reqDataPtr->listen_sock, fail_on_intr, webServerAddressList);
if (reqDataPtr->ipcFd < 0) {
return (errno > 0) ? (0 - errno) : -9999;
}
}
/*
* A connection is open. Read from the connection in order to
* get the request's role and environment. If protocol or other
* errors occur, close the connection and try again.
*/
reqDataPtr->isBeginProcessed = FALSE;
/*申请并初始化输入缓冲区*/
reqDataPtr->in = NewReader(reqDataPtr, 8192, 0);
/*内部调用read方法阻塞接收数据*/
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;
}
reqDataPtr->paramsPtr = NewParams(30);
PutParam(reqDataPtr->paramsPtr, StringCopy(roleStr));
}
SetReaderType(reqDataPtr->in, FCGI_PARAMS);
if(ReadParams(reqDataPtr->paramsPtr, reqDataPtr->in) >= 0) {
/*
* Finished reading the environment. No errors occurred, so
* leave the connection-retry loop.
*/
break;
}
/*
* Close the connection and try again.
*/
TryAgain:
FCGX_Free(reqDataPtr, 1);
} /* for (;;) */
/*
* Build the remaining data structures representing the new
* request and return successfully to the caller.
*/
SetReaderType(reqDataPtr->in, FCGI_STDIN);
/*初始化发送缓冲区,FCGX_printF函数内部会调用emptyBufferProc,
不断把数据通过ipcFd发送给客户端*/
reqDataPtr->out = NewWriter(reqDataPtr, 8192, FCGI_STDOUT);
reqDataPtr->err = NewWriter(reqDataPtr, 512, FCGI_STDERR);
reqDataPtr->nWriters = 2;
reqDataPtr->envp = reqDataPtr->paramsPtr->vec;
return 0;
}
FCGX_Finish_r;主要是清空上次申请的缓冲区
void FCGX_Finish_r(FCGX_Request *reqDataPtr)
{
int close;
if (reqDataPtr == NULL) {
return;
}
close = !reqDataPtr->keepConnection;
/* This should probably use a 'status' member instead of 'in' */
if (reqDataPtr->in) {
close |= FCGX_FClose(reqDataPtr->err);
close |= FCGX_FClose(reqDataPtr->out);
close |= FCGX_GetError(reqDataPtr->in);
}
FCGX_Free(reqDataPtr, close);
}
掌握以上内容,基本就可以用fastcgi编程了。fastcgi说到底就是对普通的套接字接口重新做了http协议的封装。整个源码十分简洁清楚。有了fastcgi接口,客户端只要按照http报文格式发送数据,服务器段调用FCGX_printF回复响应包便完成了一个完整的通信了。