新浪微博 登陆  注册   设为首页 加入收藏

学PHP >> PHP >> Windows下Nginx+fastcgi+c/c++研究

Windows下Nginx+fastcgi+c/c++研究

查看次数51605 发表时间2013-06-04 01:41:40

  fastCGI除了用于php等脚本语言解析外,还很适合搭建一个小型的web分布式计算系统,将算法部分和web应用部分独立开来。这种分离有很多好处,算法开发人员不需了解太多web知识,也能方便的发布一个web api;算...

  fastCGI除了用于php等脚本语言解析外,还很适合搭建一个小型的web分布式计算系统,将算法部分和web应用部分独立开来。这种分离有很多好处,算法开发人员不需了解太多web知识,也能方便的发布一个web api;算法部分的崩溃不会导致web网的崩溃;而且算法部分可以独立使用网格计算等先进架构。

1,web服务器Nginx

start nginx            启动Nginx服务器
nginx -s stop          // 停止nginx
nginx -s reload       // 重新加载配置文件
nginx -s quit          // 退出nginx

Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ip socket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端。这就是Nginx+FastCGI的整个运作过程。

以php解析来举例,由于Nginx本身不会对PHP进行解析,因此要实现Nginx对PHP的支持,将对PHP页面的请求交给FastCGI进程监听的IP地址及端口。如果把PHP_FPM当做动态应用服务器,那么Nginx其实就是一个反向代理服务器。Nginx通过反向代理功能实现对PHP的解析,这就是Nginx实现PHP动态解析的原理。

同理,c/c++的支持也是将cgi请求交给相应的FastCGI进程监听的IP地址和端口,这里比php更简单,并不需要解析脚本,而是直接执行cgi程序,然后返回结果给Nginx。


2,Nginx的fastCGI配置

Nginx配置文件为 nginx.conf。下面是在Nginx下支持cgi解析的一个虚拟主机配置实例。

 server {      
 	include port.conf;      
 	server_name localhost;           
  	location / {      
   		index index.html index.htm;      
   		root /html/;      
  	}       
  	location ~ .cgi$ {              
   		root           fcgi;              
   		fastcgi_pass   127.0.0.1:9000;              
   		fastcgi_index  index.cgi;              
   		fastcgi_param  SCRIPT_FILENAME  fcgi$fastcgi_script_name; 
   		include        fastcgi_params;         
  	}  
 }

location表示符合条件的域名,(这里表示所有以cgi为后缀的文件)
fastcgi_pass:IP地址和端口,就是FastCGI进程监听的IP地址和端口。
fastcgi_param SCRIPT_FILENAME :cgi文档的路径,也就是$fastcgi_script_name加上前面指定的路径,如:fcgi/myfcgiapp

上面的配置表示所有.cgi程序都发送到127.0.0.1:9000来处理,假如我们的fastcgi程序是单一功能,例如只返回“hello word”字符串,那么上面的配置文件
会导致这么一种结果:任何一个带cgi后缀的域名都返回相同的结果
     127.0.0.1/1.cgi;  127.0.0.1/2.cgi  并没有任何区别。
原因是我们写的fastCGI程序并没有像php一样解析域名后面的具体文件名,然后再调用解析后的cgi文件并返回结果,在这里我们的fastcgi只管提供自己的功能。 
因此对于任何一个网址 127.0.0.1/*.cgi 都获得同样的返回结果,都输出“hello word”,
单一cgi程序这种情况我们的配置文件可以进一步简化为:

  /my_fcgi{  
            fastcgi_pass 127.0.0.1:9000;  
            include fastcgi_params;  
       }

这样就可以用固定脚本的域名来访问,如:http://127.0.0.1/my_fcgi?arg1=100
参数直接在脚本后面接"?"来输入。
如果希望配置一个rest风格的api,可以这样设置:

   /api/{

所有这种格式的域名都会转到fastCGI来:127.0.0.1/api/fun1/abc

当然需要在fastCGI中对后面的域名做具体处理。



3,FastCGI wrapper

FastCGI wrapper 用于管理fastCGI应用程序,可以理解为方便fastCGI程序的开发,将端口监听,进程创建等一系列重复性的工作提取出来。目前网络上支持c/c++ 的wrapper主要是spawn-fcgi,他是lighttpd的一子项目,而且是在linux下开发的。
这个链接提到了在windows下直接编译spawn-fcgi的一些问题:http://redmine.lighttpd.net/boards/2/topics/686
如果直接用linux版本的代码,spawn-fcgi,fastCGI支持库都用cygwin编译,那么自己写的fastCGI程序也必须用cygwin,如果用windows版本的fastCGI库,或者用mingw编译fastCGI库,那么spawn-fcgi的源代码不能直接使用,需要把所有linux下的socket接口改为windows下的winsock32接口,再用mingw编译。
windwos下源代码下载这里:spawn-fcgi-win32.c  编译好的文件下载这里:spawn-fcgi-win32.exe

在命令行下输入spawn-fcgi能看到帮助,主要一些参数为:
-a   fastCGI绑定的TCP socket ip地址
-p   端口
-f   cgi程序
-F   指定fork多少个进程

示例:
spawn-fcgi -a 127.0.0.1 -p 9000 -f helloFastCGI

通过spawn-fcgi打开的fastCGI程序没有界面,会在系统中留下进程,要退出该进程,windowss必须进入任务管理器里手动停掉。在linux可以用下面的命令停掉:
$ netstat -anp | grep 9000 #查看占用9000端口的程序ID
$ kill -9 PID #或killall 进程名

简单分析下spawn-fcgi-win32.c的源代码,它的核心就两个部分,一是创建socket用来监听端口,另外就是创建fastcgi进程。
比较难理解的是,在创建的进程里,将socket句柄传给hStdInput,而hStdOutput和hStdError均为INVALID_HANDLE_VALUE:

   si.hStdOutput = INVALID_HANDLE_VALUE;
   si.hStdInput  = (HANDLE)fcgi_fd;
   si.hStdError  = INVALID_HANDLE_VALUE;
   if (!CreateProcess(NULL,appPath,NULL,NULL,TRUE, CREATE_NO_WINDOW,NULL,NULL,&si,&pi)) {

   那么怎么把进程的输出传给socket呢?为什么要这么设置?
后面即将提到的FastCGI库里的os_win32.c文件里,OS_LibInit函数里有一段注释:

    /*
     * Determine if this library is being used to listen for FastCGI
     * connections.  This is communicated by STDIN containing a
     * valid handle to a listener object.  In this case, both the
     * "stdout" and "stderr" handles will be INVALID (ie. closed) by
     * the starting process.
     *
     * The trick is determining if this is a pipe or a socket...
     *
     * XXX: Add the async accept test to determine socket or handle to a
     *      pipe!!!
     */ 


这段注释解释了这么设置的原因是,用来区分fastcgi还是cgi程序,只有这种方式产生的进程,才会判断为fastcgi:   

 if((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) &&
       (GetStdHandle(STD_ERROR_HANDLE)  == INVALID_HANDLE_VALUE) &&
       (GetStdHandle(STD_INPUT_HANDLE)  != INVALID_HANDLE_VALUE) ) 
 {
  ......
     if (SetNamedPipeHandleState(hListen, &pipeMode, NULL, NULL)) 
        {
            listenType = FD_PIPE_SYNC;
        } 
        else 
        {
            listenType = FD_SOCKET_SYNC;
        }
 }

int OS_IsFcgi(int sock)
{
 return (listenType != FD_UNUSED); 
}

os_win32.c文件里其实包含了spawn-fcgi-win32.c的代码,只是这些函数在fastCGI库中都没有用到。

 

4,FastCGI用c/c++编程

fastCGI 服务程序接收请求,处理然后 nginx 服务器负责将fastCGI服务返回结果转发给客户端。
fastCGI程序的编写需要用到fastCGI支持库和相关头文件,到官网www.fastcgi.com下载2.4.1版
在Windows平台上,解压后有个win32目录,用vc打开FastCGI.dsw,直接编译libfcgi 项目,获得libfcgi.dll,libfcgi.lib文件。
在我们自己的fastCGI程序中,链接上libfcgi.lib,包含fcgi-2.4.1/include目录即可。
运行时将fastcgi.dll和自己生成的fastCGI程序一起放入spawn-fcgi目录中,用上面的spawn-fcgi命令启动。

fastcgi库里带的一个示例程序(echo.c,略删减):

#include "fcgi_config.h"
#include <stdlib.h>
#ifdef _WIN32
#include <process.h>
#else
extern char **environ;
#endif
#include "fcgi_stdio.h"

static void PrintEnv(char *label, char **envp)
{
    printf("%s:<br>
<pre>
", label);
    for ( ; *envp != NULL; envp++) {
        printf("%s
", *envp);
    }
    printf("</pre><p>
");
}

int main ()
{
    int count = 0;
    while (FCGI_Accept() >= 0) {
 printf("Content-type: text/html
"
     "
"
     "<title>FastCGI echo</title>"
     "<h1>FastCGI echo</h1>
"
            "Request number %d,  Process ID: %d<p>
", ++count, getpid());

        PrintEnv("Request environment", environ);
    } /* while */
    return 0;
}

这个程序比单纯的在浏览器里显示 “hello world” 的程序更有代表性,我在windows xp下无法获得正确的Request环境变量,而在linux下是完全正确的。
只输出“hello world”的程序在windows和linux下都正确。

这个程序试图读取从web服务器传过来的Request环境变量。这些环境变量是程序里必须要处理的,例如脚本名称,参数等等。
先把这个程序放一放,编译另一个自带的例子程序echo_x.c,这个是用更底层的API调用(带X的函数),

#include "fcgi_config.h"
#include <stdlib.h>
#ifdef _WIN32
#include <process.h>
#else
extern char **environ;
#endif
#include "fcgiapp.h"

static void PrintEnv(FCGX_Stream *out, char *label, char **envp)
{
    FCGX_FPrintF(out, "%s:<br>
<pre>
", label);
    for( ; *envp != NULL; envp++) {
        FCGX_FPrintF(out, "%s
", *envp);
    }
    FCGX_FPrintF(out, "</pre><p>
");
}
int main ()
{
    FCGX_Stream *in, *out, *err;
    FCGX_ParamArray envp;
    int count = 0;

    while (FCGX_Accept(&in, &out, &err, &envp) >= 0) {
        FCGX_FPrintF(out,
           "Content-type: text/html
"
           "
"
           "<title>FastCGI echo (fcgiapp version)</title>"
           "<h1>FastCGI echo (fcgiapp version)</h1>
"
     "Request number %d,  Process ID: %d<p>
", ++count, getpid());
        PrintEnv(out, "Request environment", envp);

    } /* while */
    return 0;
}

在浏览器地址栏里输入下面的字符串:
http://127.0.0.1/abc.cgi?arg=9racaa

可以看到输出结果:

SCRIPT_FILENAME=fcgi/abc.cgi
QUERY_STRING=arg=9racaa
REQUEST_METHOD=GET
CONTENT_TYPE=
CONTENT_LENGTH=
SCRIPT_NAME=/abc.cgi
REQUEST_URI=/abc.cgi?arg=9racaa
DOCUMENT_URI=/abc.cgi

.....

这两个程序区别很小,第一个版本为了兼容传统的CGI程序,对带X的API重新封装而已,别且重新定义了printf等标准输入输出函数,这样以前写的CGI程序不用做什么改变就可以直接变为FastCGI。

到fcgi_stdio.c里查看FCGI_Accept的源代码:

int FCGI_Accept(void)
{
    if(!acceptCalled) {
        /*
         * First call to FCGI_Accept.  Is application running
         * as FastCGI or as CGI?
         */
        isCGI = FCGX_IsCGI();
        acceptCalled = TRUE;
        atexit(&FCGI_Finish);
    } else if(isCGI) {
        /*
         * Not first call to FCGI_Accept and running as CGI means
         * application is done.
         */
        return(EOF);
    }
    if(isCGI) {
        FCGI_stdin->stdio_stream = stdin;
        FCGI_stdin->fcgx_stream = NULL;
        FCGI_stdout->stdio_stream = stdout;
        FCGI_stdout->fcgx_stream = NULL;
        FCGI_stderr->stdio_stream = stderr;
        FCGI_stderr->fcgx_stream = NULL;
    } else {
        FCGX_Stream *in, *out, *error;
        FCGX_ParamArray envp;
        int acceptResult = FCGX_Accept(&in, &out, &error, &envp);
        if(acceptResult < 0) {
            return acceptResult;
        }
        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;
}

如果是CGI程序,那么while循环调用FCGI_Accept就变为只有一次调用,第二次就退出了,和原来的CGI模式完全一致,输入输出也是原来的标准输入输出。
如果是FastCGI,则不退出程序,循环接受请求,并且重新定义输入输出。这也是fastCGI效率更高的原因,而且可扩展性更好,能够分开部署web服务器和fastCGI程序。

/* Instead of using operating system environment variables and pipes, 
the FastCGI protocol multiplexes the environment information, standard input, output and error 
over a single full-duplex connection. This allows FastCGI programs to run on remote machines, 
using TCP connections between the Web server and the FastCGI application */

FCGI_Accept函数中 environ = envp; 这个赋值不理解,如果是把envp直接传给应用程序,是可以打印出来的,
而现在这个赋值给stdlib里一个全局变量,实际运行来看并没有起到作用,望高手解惑。

 

 


(转发请注明转自:学PHP)    


  相关推荐




  发表评论
昵称:
(不超过20个字符或10个汉字)
内容: