威尼斯手机平台登陆-官方网站登录

威尼斯手机平台登陆为您带来世界三大博彩公司最新相关资讯,威尼斯官方网站登录充分考虑到不同地域网民的不同需求,威尼斯手机平台登陆良好的用户界面,人性化的操作,实用的功能设计使其广泛受到欢迎,推动实体出版、影视、动漫、游戏等相关文化产业的发展。

您的位置:威尼斯手机平台登陆 > 前端资源 > 不得不说传统的 CGI,这些内置实现的物理位置在PHP源码的SAPI目录

不得不说传统的 CGI,这些内置实现的物理位置在PHP源码的SAPI目录

发布时间:2020-03-04 19:11编辑:前端资源浏览(156)

    在商议 法斯特CGI 在此之前,不能不说守旧的 CGI 的做事规律,同一时候应该大致明白 CGI 1.1 协议

     本文链接:

    守旧 CGI 职业原理分析

    顾客端访谈有些 U汉兰达L 地址然后,通过 GET/POST/PUT 等艺术交给数据,并由此HTTP 左券向 Web 服务器发出供给,服务器端的 HTTP Daemon(守护进度)将 HTTP 诉求里描述的音讯通过标准输入 stdin 和景况变量(environment variable卡塔尔传递给主页钦命的 CGI 程序,并运维此应用程序实行拍卖(包含对数据库的管理),管理结果通过正式输出 stdout 再次回到给 HTTP Daemon 守护进度,再由 HTTP Daemon 进程经过 HTTP 合同再次来到给顾客端。

    上面的这段话精通也许依旧相比较抽象,上面大家就经过三回GET诉求为例进行详细表达。

    图片 1

    上面用代码来落到实处图中公布的成效。Web 服务器运维四个 socket 监听服务,然后在该地实施 CGI 程序。后边有比较详细的代码解读。

    1、在PHP生命周期的顺序阶段,一些与劳动相关的操作都以由此SAPI接口达成。那个内置完成的物理地点在PHP源码的SAPI目录。这么些目录寄放了PHP对一一服务器抽象层的代码,比方命令路程序的贯彻,Apache的mod_php模块完成以至fastcgi的落到实处等等

    Web 服务器代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <string.h>
    
    #define SERV_PORT 9003
    
    char* str_join(char *str1, char *str2);
    char* html_response(char *res, char *buf);
    
    int main(void)
    {
        int lfd, cfd;
        struct sockaddr_in serv_addr,clin_addr;
        socklen_t clin_len;
        char buf[1024],web_result[1024];
        int len;
        FILE *cin;
    
        if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
            perror("create socket failed");
            exit(1);
        }
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(SERV_PORT);
    
        if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        {
            perror("bind error");
            exit(1);
        }
    
        if(listen(lfd, 128) == -1)
        {
            perror("listen error");
            exit(1);
        }
    
        signal(SIGCLD,SIG_IGN);
    
        while(1)
        {
            clin_len = sizeof(clin_addr);
            if ((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)
            {
                perror("接收错误n");
                continue;
            }
    
            cin = fdopen(cfd, "r");
            setbuf(cin, (char *)0);
            fgets(buf,1024,cin); //读取第一行
            printf("n%s", buf);
    
            //============================ cgi 环境变量设置演示 ============================
    
            // 例如 "GET /user.cgi?id=1 HTTP/1.1";
    
            char *delim = " ";
            char *p;
            char *method, *filename, *query_string;
            char *query_string_pre = "QUERY_STRING=";
    
            method = strtok(buf,delim);         // GET
            p = strtok(NULL,delim);             // /user.cgi?id=1 
            filename = strtok(p,"?");           // /user.cgi
    
            if (strcmp(filename,"/favicon.ico") == 0)
            {
                continue;
            }
    
            query_string = strtok(NULL,"?");    // id=1
            putenv(str_join(query_string_pre,query_string));
    
            //============================ cgi 环境变量设置演示 ============================
    
            int pid = fork();
    
            if (pid > 0)
            {
                close(cfd);
            }
            else if (pid == 0)
            {
                close(lfd);
                FILE *stream = popen(str_join(".",filename),"r");
                fread(buf,sizeof(char),sizeof(buf),stream);
                html_response(web_result,buf);
                write(cfd,web_result,sizeof(web_result));
                pclose(stream);
                close(cfd);
                exit(0);
            }
            else
            {
                perror("fork error");
                exit(1);
            }
        }
    
        close(lfd);
    
        return 0;
    }
    
    char* str_join(char *str1, char *str2)
    {
        char *result = malloc(strlen(str1)+strlen(str2)+1);
        if (result == NULL) exit (1);
        strcpy(result, str1);
        strcat(result, str2);
    
        return result;
    }
    
    char* html_response(char *res, char *buf)
    {
        char *html_response_template = "HTTP/1.1 200 OKrnContent-Type:text/htmlrnContent-Length: %drnServer: mengkangrnrn%s";
    
        sprintf(res,html_response_template,strlen(buf),buf);
    
        return res;
    }
    

    在逐个服务器抽象层之间据守着同一的预约,这里大家称之为SAPI接口。各样SAPI完毕都以二个_sapi_module_struct构造体变量。(SAPI接口卡塔尔(قطر‎。在PHP的源码中,当必要调用服务器相关音信时,全体通过SAPI接口中对应的点子调用实现,而那个格局在挨门挨户服务器抽象层落成时都会有独家的贯彻。由于繁多操作的通用性,有不小学一年级部分接口方法应用的是暗中认可方法。下图为SPAI的洗练含蓄表示图

    如上代码中的重视:

    • 66~81行找到CGI程序的相对路线(大家为了轻便,直接将其根目录定义为Web程序的当前目录),那样就足以在子进度中施行CGI 程序了;同期安装蒙受变量,方便CGI程序运营时读取;
    • 94~95行将 CGI 程序的正规输出结果写入 Web 服务器守护进度的缓存中;
    • 97行则将包裹后的 html 结果写入客商端 socket 描述符,重返给连接Web服务器的顾客端。

    图片 2

    CGI 程序(user.c)

    #include <stdio.h>
    #include <stdlib.h>
    // 通过获取的 id 查询用户的信息
    int main(void){
    
        //============================ 模拟数据库 ============================
        typedef struct 
        {
            int  id;
            char *username;
            int  age;
        } user;
    
        user users[] = {
            {},
            {
                1,
                "mengkang.zhou",
                18
            }
        };
        //============================ 模拟数据库 ============================
    
        char *query_string;
        int id;
    
        query_string = getenv("QUERY_STRING");
    
        if (query_string == NULL)
        {
            printf("没有输入数据");
        } else if (sscanf(query_string,"id=%d",&id) != 1)
        {
            printf("没有输入id");
        } else
        {
            printf("用户信息查询<br>学号: %d<br>姓名: %s<br>年龄: %d",id,users[id].username,users[id].age);
        }
    
        return 0;
    }
    

    将上边包车型地铁 CGI 程序编写翻译成gcc user.c -o user.cgi,放在上边web程序的同级目录。

    代码中的第28行,从情状变量中读取后面在Web服务器守护进程中设置的意况变量,是大家演示的根本。

    以cgi情势和apache2服务器为例,它们的开发银行方法如下:

    法斯特CGI 工作规律分析

    相持于 CGI/1.1 标准在 Web 服务器在该地 fork 一个子进度施行 CGI 程序,填充 CGI 预约义的遭受变量,放入系统情状变量,把 HTTP body 体的 content 通过专门的学业输入传入子进度,管理达成之后经过正规输出再次来到给 Web 服务器。法斯特CGI 的着力则是不许古板的 fork-and-execute 格局,收缩每一趟运维的英豪开销(后边以 PHP 为例表达),以常驻的法子来拍卖央浼。

    法斯特CGI 职业流程如下:

    1. 法斯特CGI 进程微机自个儿开始化,运营三个 CGI 解释器进度,并听候来自 Web Server 的接连几日。
    2. Web 服务器与 法斯特CGI 进度微机进行 Socket 通讯,通过 FastCGI 磋商发送 CGI 情况变量和正式输入数据给 CGI 解释器进度。
    3. CGI 解释器进程达成管理后将行业内部输出和错误信息从同再三再四接重回 Web Server。
    4. CGI 解释器进度接着等待并拍卖来自 Web Server 的下一个连连。

    图片 3

    法斯特CGI 与金钱观 CGI 格局的分别之一则是 Web 服务器不是一向实践 CGI 程序了,而是经过 socket 与 法斯特CGI 响应器(法斯特CGI 进度微处理机)实行相互,Web 服务器需求将 CGI 接口数据封装在遵纪守法 法斯特CGI 公约包中发送给 法斯特CGI 响应器程序。便是由于 FastCGI 进度管理器是依照socket 通讯的,所以也是分布式的,Web服务器和CGI响应器服务器分开安排。

    再啰嗦一句,法斯特CGI 是一种公约,它是起家在CGI/1.1幼功之上的,把CGI/1.1之中的要传递的数码通过法斯特CGI合同定义的次第、格式实行传递。

    cgi_sapi_module.startup(&cgi_sapi_module) // cgi模式 cgi/cgi_main.c文件
    
    apache_sapi_module.startup(&apache_sapi_module); // apache服务器  apache2handler/sapi_apache2.c文件
    

    预备专业

    莫不上边的开始和结果明白起来依旧很虚幻,那是由于第一对法斯特CGI合同还不曾二个大概的认知,第二一向不实际代码的上学。所以供给事情发生前学习下 法斯特CGI 协议的剧情,不自然必要完全看懂,可大致精通之后,看完本篇再结合着读书驾驭消食。

    http://www.fastcgi.com/devkit… (保加巴塞尔语原版)
    http://andylin02.iteye.com/bl… (中文版)

    这里的cgi_sapi_module是sapi_module_struct结构体的静态变量。它的startup方法指向php_cgi_startup函数指针。在此个布局体中除去startup函数指针,还会有大多此外办法或字段,这一个布局在服务器的接口达成中都有定义

    法斯特CGI 左券解析

    下边结合 PHP 的 法斯特CGI 的代码举行剖释,不作特殊表达以下代码均来自于 PHP 源码。

     

    法斯特CGI 新闻类型

    法斯特CGI 将传输的信息做了过多档案的次序的细分,其协会体定义如下:

    typedef enum _fcgi_request_type {
        FCGI_BEGIN_REQUEST      =  1, /* [in]                              */
        FCGI_ABORT_REQUEST      =  2, /* [in]  (not supported)             */
        FCGI_END_REQUEST        =  3, /* [out]                             */
        FCGI_PARAMS             =  4, /* [in]  environment variables       */
        FCGI_STDIN              =  5, /* [in]  post data                   */
        FCGI_STDOUT             =  6, /* [out] response                    */
        FCGI_STDERR             =  7, /* [out] errors                      */
        FCGI_DATA               =  8, /* [in]  filter data (not supported) */
        FCGI_GET_VALUES         =  9, /* [in]                              */
        FCGI_GET_VALUES_RESULT  = 10  /* [out]                             */
    } fcgi_request_type;
    

    全体SAPI相近于多少个面向对象中的模板方法格局的施用。SAPI.c和SAPI.h文件所满含的一部分函数正是模板方法方式中的抽象模板,各种服务器对于sapi_module的定义及连锁兑现则是叁个个切实的模板

    音信的发送顺序

    下图是四个粗略的音信传递流程

    图片 4

    首首发送的是FCGI_BEGIN_REQUEST,然后是FCGI_PARAMSFCGI_STDIN,由于种种音讯头(上面将详细表明)里面能够承袭的最大尺寸是65535,所以那三种等级次序的音信不必然只发送一回,有一点都不小大概一连发送多次。

    法斯特CGI 响应体管理完成之后,将发送FCGI_STDOUTFCGI_STDERR,同理也恐怕数次连接发送。最终以FCGI_END_REQUEST代表需要的竣事。

    需求专心的有个别,FCGI_BEGIN_REQUESTFCGI_END_REQUEST分别标记着乞求的开头和告竣,与成套合同息息相关,所以她们的音信体的剧情也是研讨的一有的,因而也是有对应的布局体与之对应(前面会详细表明)。而情况变量、标准输入、规范输出、错误输出,那些都以业务相关,与磋商非亲非故,所以他们的新闻体的内容则无布局体对应。

    由于整个音讯是二进制接二连三传递的,所以必需定义三个联结的协会的音信头,那样以便读取每个消息的音信体,方便音信的切割。那在网络通信中是极其广泛的一种手腕。

     

    FastCGI 消息头

    如上,法斯特CGI 新闻分10种新闻类型,有的是输入过多输出。而具备的消息都以二个信息头最早。其协会体定义如下:

    typedef struct _fcgi_header {
        unsigned char version;
        unsigned char type;
        unsigned char requestIdB1;
        unsigned char requestIdB0;
        unsigned char contentLengthB1;
        unsigned char contentLengthB0;
        unsigned char paddingLength;
        unsigned char reserved;
    } fcgi_header;
    

    字段解释下:

    • version标记法斯特CGI左券版本。
    • type 标志法斯特CGI记录类型,也正是记录实施的日常意义。
    • requestId标志记录所属的法斯特CGI央浼。
    • contentLength笔录的contentData组件的字节数。

    关于地方的xxB1xxB0的情商表达:当四个相邻的构造组件除了后缀“B1”和“B0”之外命名类似有的时候候,它表示那多少个构件可身为价值评估为B1<<8 + B0的单个数字。该单个数字的名字是那个组件减去后缀的名字。那些约定归咎了一个由超越七个字节表示的数字的管理格局。

    比如合同头中requestIdcontentLength意味着的最大值正是65535

    #include <stdio.h>
    #include <stdlib.h>
    #include <limits.h>
    
    int main()
    {
       unsigned char requestIdB1 = UCHAR_MAX;
       unsigned char requestIdB0 = UCHAR_MAX;
       printf("%dn", (requestIdB1 << 8) + requestIdB0); // 65535
    }
    

    您或者会想到若是几个音信体长度超过65535如何是好,则分割为多个相似档案的次序的消息发送即可。

    2、Apache模块

    FCGI_BEGIN_REQUEST 的定义

    typedef struct _fcgi_begin_request {
        unsigned char roleB1;
        unsigned char roleB0;
        unsigned char flags;
        unsigned char reserved[5];
    } fcgi_begin_request;
    

    字段解释

    role表示Web服务器期望利用扮演的剧中人物。分为多个剧中人物(而我们这里研讨的情景相仿都是响应器剧中人物)

    typedef enum _fcgi_role {
        FCGI_RESPONDER    = 1,
        FCGI_AUTHORIZER    = 2,
        FCGI_FILTER        = 3
    } fcgi_role;
    

    FCGI_BEGIN_REQUEST中的flags组件包蕴三个调控线路关闭的位:flags & FCGI_KEEP_CONN:假设为0,则利用在对此次诉求响应后关闭线路。若是非0,应用在对此番必要响应后不会停业线路;Web服务器为线路保持响应性。

    (1卡塔尔当PHP要求在Apache服务器下运作时,日常的话,它可以mod_php5模块的款型集成,那时mod_php5模块的信守是选用Aapche传递过来的PHP文件伏乞,并管理这几个央求,然后将拍卖后的结果回到给Apache。倘诺我们在Apache运营前在其配置文件中配置了PHP模块,PHP模块通过挂号apache2的ap_hook_post_config挂钩,在Apache运维的时候运行此模块以吸纳PHP文件的乞求。

    FCGI_END_REQUEST 的定义

    typedef struct _fcgi_end_request {
        unsigned char appStatusB3;
        unsigned char appStatusB2;
        unsigned char appStatusB1;
        unsigned char appStatusB0;
        unsigned char protocolStatus;
        unsigned char reserved[3];
    } fcgi_end_request;
    

    字段解释

    appStatus组件是应用级其余状态码。
    protocolStatus构件是讨论级其余状态码;protocolStatus的值或然是:

    FCGI_REQUEST_COMPLETE:乞求的正规甘休。
    FCGI_CANT_MPX_CONN:拒绝新央求。这发生在Web服务器通过一条线路向利用发送并发的呼吁时,后面一个被规划为每条路径每便管理一个需要。
    FCGI_OVEGL450LOADED:推却新诉求。那产生在采纳用完有些财富时,比方数据库连接。
    FCGI_UNKNOWN_ROLE:屏绝新乞求。那爆发在Web服务器钦命了叁个接纳无法辨识的剧中人物时。

    protocolStatus在 PHP 中的定义如下

    typedef enum _fcgi_protocol_status {
        FCGI_REQUEST_COMPLETE    = 0,
        FCGI_CANT_MPX_CONN        = 1,
        FCGI_OVERLOADED            = 2,
        FCGI_UNKNOWN_ROLE        = 3
    } dcgi_protocol_status;
    

    亟待注意dcgi_protocol_statusfcgi_role次第要素的值都以 法斯特CGI 合同里定义好的,而非 PHP 自定义的。

    除去这种运维时的加载方式,Apache的模块能够在运维的时候动态装载,那代表对服务器能够张开作用扩充而没有须求再一次对源代码实行编写翻译,以致不须求重启服务器。大家所供给做的可是是给服务器发送时限信号HUP或然AP_SIG_GEACEFUL公告服务注重新载入模块。但是在动态装载在此之前我们必要将模块编写翻译成为动态链接库。那个时候的动态加载便是加载动态链接库。Apache中对动态链接库的拍卖是经过模块mod_so来达成的,因而mod_so模块无法被动态加载,它一定要本静态编写翻译进Apache的着力。那表示它和Apache一同运行的。

    新闻报纸发表样例

    为了简单的象征,新闻头只展现新闻的项目和音讯的 id,其余字段都不赋予体现。上边包车型地铁例子来自于官方网站

    {FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}
    {FCGI_PARAMS,          1, "1302SERVER_PORT801316SERVER_ADDR199.170.183.42 ... "}
    {FCGI_STDIN,           1, "quantity=100&item=3047936"}
    {FCGI_STDOUT,          1, "Content-type: text/htmlrnrn<html>n<head> ... "}
    {FCGI_END_REQUEST,     1, {0, FCGI_REQUEST_COMPLETE}}
    

    同盟地点各种构造体,则足以大要想到 法斯特CGI 响应器的剖判和响应流程:

    先是读取音讯头,获得其品种为FCGI_BEGIN_REQUEST,然后分析其音讯体,得悉其急需的角色正是FCGI_RESPONDERflag为0,表示供给结束后关闭线路。然后深入深入分析第二段新闻,得悉其新闻类型为FCGI_PARAMS,然后直接将信息体里的开始和结果以回车符切割后存入情况变量。与之临近,管理完毕之后,则赶回了FCGI_STDOUT音讯体和FCGI_END_REQUEST消息体供 Web 服务器深入剖析。

     

    PHP 中的 FastCGI 的实现

    下面对代码的解读笔记只是自个儿个人文化的三个梳理提炼,如有校正,请大家提出。对不熟练该代码的同桌来讲恐怕是二个教导,最初认识,借使感到很模糊不明晰,那么还是须要本身逐行去阅读。

    php-src/sapi/cgi/cgi_main.c为例举行深入分析表达,即便开辟景况为 unix 情形。main 函数中有个别变量的概念,以至 sapi 的初叶化,我们就不研讨在此边研讨了,只表明有关 法斯特CGI 相关的原委。

    Apache是怎么着加载模块的啊?以mod_php5为例,首先在httpd.conf中增加一行:

    1.打开一个 socket 监听服务

    fcgi_fd = fcgi_listen(bindpath, 128);
    

    今后处发轫监听,而fcgi_listen函数里面则成功 socket 服务前三步socket,bind,listen

    LoadModule php5_module modules/mod_php5.so
    

    2.初始化诉求对象

    fcgi_request对象分配内部存款和储蓄器,绑定监听的 socket 套接字。

    fcgi_init_request(&request, fcgi_fd);
    

    全体诉求从输入到再次来到,都围绕着fcgi_request布局体对象在进展。

    typedef struct _fcgi_request {
        int            listen_socket;
        int            fd;
        int            id;
        int            keep;
        int            closed;
    
        int            in_len;
        int            in_pad;
    
        fcgi_header   *out_hdr;
        unsigned char *out_pos;
        unsigned char  out_buf[1024*8];
        unsigned char  reserved[sizeof(fcgi_end_request_rec)];
    
        HashTable     *env;
    } fcgi_request;
    

    在配置文件中加多了所示的通令后,Apache在加载模块时会依照模块名查找模块并加载。Apache的每二个模块都以以module布局体的款型存在,module构造的name属性在结尾是通过宏STANDASportageD20_MODULE_STUFF以__FILE__反映。通过以前的一声令下中钦定的路子找到相关的动态链接库文件后,Apache通过内部的函数获取动态链接库中的内容,并将模块的故事情节加载到内部存储器中钦赐变量中。

    3.创办四个 CGI 拆解解析器子进度

    这里子进度的个数暗中认可是0,从布署文件中读取设置各处境变量,然后在前后相继中读取,然后创设内定数量的子进度来等待处理Web 服务器的伸手。

    if (getenv("PHP_FCGI_CHILDREN")) {
        char * children_str = getenv("PHP_FCGI_CHILDREN");
        children = atoi(children_str);
        ...
    }
    
    do {
        pid = fork();
        switch (pid) {
        case 0:
            parent = 0; // 将子进程中的父进程标识改为0,防止循环 fork
    
            /* don't catch our signals */
            sigaction(SIGTERM, &old_term, 0);
            sigaction(SIGQUIT, &old_quit, 0);
            sigaction(SIGINT,  &old_int,  0);
            break;
        case -1:
            perror("php (pre-forking)");
            exit(1);
            break;
        default:
            /* Fine */
            running++;
            break;
        }
    } while (parent && (running < children));
    

    在真正激活模块早先,Apache会检查有着加载的模块是还是不是为确实的Apache模块。最后Apache会调用相关的函数(ap_add_loaded_moduleState of Qatar将模块激活,此处的激活正是将模块放入相应的链表中(ap_top_modules链表)

    4.在子进度中接到央求

    到那边整个都仍然 socket 的劳动的老路。选用须求,然后调用了fcgi_read_request

    fcgi_accept_request(&request)
    
    int fcgi_accept_request(fcgi_request *req)
    {
        int listen_socket = req->listen_socket;
        sa_t sa;
        socklen_t len = sizeof(sa);
        req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
    
        ...
    
        if (req->fd >= 0) {
            // 采用多路复用的机制
            struct pollfd fds;
            int ret;
    
            fds.fd = req->fd;
            fds.events = POLLIN;
            fds.revents = 0;
            do {
                errno = 0;
                ret = poll(&fds, 1, 5000);
            } while (ret < 0 && errno == EINTR);
            if (ret > 0 && (fds.revents & POLLIN)) {
                break;
            }
            // 仅仅是关闭 socket 连接,不清空 req->env
            fcgi_close(req, 1, 0);
        }
    
        ...
    
        if (fcgi_read_request(req)) {
            return req->fd;
        }
    }
    

    并且把request放入全局变量sapi_globals.server_context,这一点很关键,方便了在其余地点对央求的调用。

    SG(server_context) = (void *) &request;
    

    Apache加载的是PHP模块,那么那一个模块时怎么落到实处的吗?Apache2的mod_php5模块满含sapi/apache2handler和sapi/apache2filter多少个目录,在apache2_handle/mod_php5.c文件中,模块定义的相关代码如下:

    5.读取数据

    上边包车型客车代码删除一些极度情状的管理,只突显了健康景况下实施顺序。

    fcgi_read_request中则达成大家在音讯广播发表样例中的消息读取,而当中多数的len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;操作,已经在前边的法斯特CGI 音讯头中解释过了。

    那边是剖判 法斯特CGI 合同的非常重要。

    static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count)
    {
        int    ret;
        size_t n = 0;
    
        do {
            errno = 0;
            ret = read(req->fd, ((char*)buf)+n, count-n);
            n += ret;
        } while (n != count);
        return n;
    }
    
    static int fcgi_read_request(fcgi_request *req)
    {
        ...
    
        if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr.version < FCGI_VERSION_1) {
            return 0;
        }
    
        len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
        padding = hdr.paddingLength;
    
        req->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0;
    
        if (hdr.type == FCGI_BEGIN_REQUEST && len == sizeof(fcgi_begin_request)) {
            char *val;
    
            if (safe_read(req, buf, len+padding) != len+padding) {
                return 0;
            }
    
            req->keep = (((fcgi_begin_request*)buf)->flags & FCGI_KEEP_CONN);
    
            switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) {
                case FCGI_RESPONDER:
                    val = estrdup("RESPONDER");
                    zend_hash_update(req->env, "FCGI_ROLE", sizeof("FCGI_ROLE"), &val, sizeof(char*), NULL);
                    break;
                ...
                default:
                    return 0;
            }
    
            if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr.version < FCGI_VERSION_1) {
                return 0;
            }
    
            len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
            padding = hdr.paddingLength;
    
            while (hdr.type == FCGI_PARAMS && len > 0) {
                if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || hdr.version < FCGI_VERSION_1) {
                    req->keep = 0;
                    return 0;
                }
                len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;
                padding = hdr.paddingLength;
            }
    
            ...
        }
    }
    
    AP_MODULE_DECLARE_DATA module php5_module = {
        STANDARD20_MODULE_STUFF,
            /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现*/
        create_php_config,      /* create per-directory config structure */
        merge_php_config,       /* merge per-directory config structures */
        NULL,                   /* create per-server config structure */
        NULL,                   /* merge per-server config structures */
        php_dir_cmds,           /*模块定义的所有命令*/
        php_ap2_register_hook  /*注册钩子,此函数通过ap_hoo_开头的函数在一次处理过程中对于指定的步骤注册钩子*/
    };
    

    6.实施脚本

    假若本次须要为PHP_MODE_STANDARD则会调用php_execute_script施行PHP文件。这里就不开展了。

    它所对应的是Apache的module布局,module的协会定义如下:

    7.甘休央求

    fcgi_finish_request(&request, 1);
    
    int fcgi_finish_request(fcgi_request *req, int force_close)
    {
        int ret = 1;
    
        if (req->fd >= 0) {
            if (!req->closed) {
                ret = fcgi_flush(req, 1);
                req->closed = 1;
            }
            fcgi_close(req, force_close, 1);
        }
        return ret;
    }
    

    fcgi_finish_request中调用fcgi_flushfcgi_flush中封装一个FCGI_END_REQUEST消息体,再通过safe_write写入 socket 连接的客户端描述符。

    typedef struct module_struct module;
    struct module_struct {
        int version;
        int minor_version;
        int module_index;
        const char *name;
        void *dynamic_load_handle;
        struct module_struct *next;
        unsigned long magic;
        void (*rewrite_args) (process_rec *process);
        void *(*create_dir_config) (apr_pool_t *p, char *dir);
        void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
        void *(*create_server_config) (apr_pool_t *p, server_rec *s);
        void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void 
    *new_conf);
        const command_rec *cmds;
        void (*register_hooks) (apr_pool_t *p);
    }
    

    8.规范输入规范输出的管理

    正式输入和标准输出在地方未有同台议论,实际在cgi_sapi_module构造体中有定义,不过cgi_sapi_module这个sapi_module_struct构造体与此外代码耦合太多,笔者自个儿也没深切的敞亮,这里大约做下相比较,希望其他网络好友给与指点、补充。

    cgi_sapi_module中定义了sapi_cgi_read_post来管理POST数据的读取.

    while (read_bytes < count_bytes) {
        fcgi_request *request = (fcgi_request*) SG(server_context);
        tmp_read_bytes = fcgi_read(request, buffer + read_bytes, count_bytes - read_bytes);
        read_bytes += tmp_read_bytes;
    }
    

    fcgi_read中则对FCGI_STDIN的数量开展读取。
    同时cgi_sapi_module中定义了sapi_cgibin_ub_write来接管道输送出管理,而内部又调用了sapi_cgibin_single_write,最终完结了FCGI_STDOUT 法斯特CGI 数据包的封装.

    fcgi_write(request, FCGI_STDOUT, str, str_length);
    

     

    写在终极

    把 法斯特CGI 的知识学习精晓的进度做了这么一篇笔记,把团结清楚的剧情(自己以为)有系统地写出来,能够令人家比较便于看领会也是一件不挺不易于的事。同不经常间也让自身对那一个知识点的理解又尖锐了一层。对 PHP 代码学习精晓中还或者有非常多郁结之处还索要自己要好中期慢慢消化摄取和透亮。

    正文都以友好的一对精晓,水平有限,如有考订,希望我们授予指正。

    咬牙看完本的都以老行驶员,说真话,前面有个别太枯燥了!如若能把种种知识点真正通晓消化,相对有超大的收获。

    下边包车型客车模块布局与大家在mod_php5.c中所看见的布局有好几分化,这是由于STANDA福特ExplorerD20_MODULE_STUFF的由来,这几个宏它包涵了前头8个字段的定义。STANDA本田UR-VD20_MODULE_STUFF宏的定义如下:

    /** Use this in all standard modules */
    #define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, 
                    MODULE_MAGIC_NUMBER_MINOR, 
                    -1, 
                    __FILE__, 
                    NULL, 
                    NULL, 
                    MODULE_MAGIC_COOKIE, 
                                    NULL      /* rewrite args spot */
    

    在php5_module定义的结构中,php_dir_cmds是模块定义的兼具的吩咐会集,定义的内容如下:

    const command_rec php_dir_cmds[] =
    {
        AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,
            OR_OPTIONS, "PHP Value Modifier"),
        AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,
            OR_OPTIONS, "PHP Flag Modifier"),
        AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,
            NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"),
        AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
            NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
        AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,
            RSRC_CONF, "Directory containing the php.ini file"),
        {NULL}
    };
    

     

    这是mod_php5模块定义的指令表。它实际上是二个commond_rec构造的数组。当Apache遭受指令的时候将依次遍历各种模块中的指令表,查找是不是有万分模块能够处理该指令,借使找到,则调用响应的管理函数,要是持有指令表中的模块都无法处理该指令,那么将报错,如上所见,mod_php5模块仅提供php_value等5个指令。

    php_ap2_register_hook函数的定义如下:

    void php_ap2_register_hook(apr_pool_t *p)
    {
        ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
        ap_hook_post_config(php_apache_server_startup, NULL, NULL, 
    APR_HOOK_MIDDLE);
        ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
        ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    }
    

    如上代码表明了pre_config,post_config,handler和child_init4个关系以致相应的管理函数。当中pre_config,post_config,child_init是开发银行挂钩,它们在服务器运营时调用。handler挂钩是央求挂钩,它在服务器管理需要时调用。此中在post_config挂钩中运维php。它经过php_apache_server_startup函数达成,php_apache_server_startup函数通过调用sapi_startup运行sapi,并经过调用php_apache2_startup来注册sapi module struct,最后调用php_module_startup开端化php,当中又会早先化Zend引擎,以至填充zend_module_struct中的treat_data成员(通过php_startup_sapi_content_types)等。

      到那边,大家理解了Apache加载mod_php5模块的上上下下经过,然则那个进度与我们的饿SAPI有如何关联吧?mod_php5也定义了归属Apache的sapi_module_struct结构:

    static sapi_module_struct apache2_sapi_module = {
    "apache2handler",
    "Apache 2.0 Handler",
    
    php_apache2_startup,                /* startup */
    php_module_shutdown_wrapper,            /* shutdown */
    
    NULL,                       /* activate */
    NULL,                       /* deactivate */
    
    php_apache_sapi_ub_write,           /* unbuffered write */
    php_apache_sapi_flush,              /* flush */
    php_apache_sapi_get_stat,           /* get uid */
    php_apache_sapi_getenv,             /* getenv */
    php_error,                  /* error handler */
    
    php_apache_sapi_header_handler,         /* header handler */
    php_apache_sapi_send_headers,           /* send headers handler */
    NULL,                       /* send header handler */
    
    php_apache_sapi_read_post,          /* read POST data */
    php_apache_sapi_read_cookies,           /* read Cookies */
    
    php_apache_sapi_register_variables,
    php_apache_sapi_log_message,            /* Log message */
    php_apache_sapi_get_request_time,       /* Request Time */
    NULL,                       /* Child Terminate */
    
    STANDARD_SAPI_MODULE_PROPERTIES
    };
    

     

    那几个方法都归属Apache服务器,以读取cookie为例,当大家在Apache服务器碰着下,在PHP中调用读取Cookie时,最后收获的数码的任务是在激活SAPI时,它所调用的方法是read_cookie。

    SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
    

    对此每三个服务器在加载时,大家都钦定了sapi_module,而Apache的sapi_module是apache2_sapi_module。在那之中对应read_cookie的艺术是php_apache_sapi_read_cookie函数。那也是定义SAPI布局的说辞:统一接口,面向接口编程,具备更加好的扩张性和适应性。

    (2卡塔尔Apache的周转进程

    Apache的周转让承包蕴运转阶段和平运动转阶段,运行阶段Apache以root完毕运行,整个经过处于单进度单线程的境况中,那些品级包涵布署文件剖判、模块加载、系统能源起头化(比如日志文件、共享内部存款和储蓄器段、数据库连接等卡塔尔等职业。

    在运作阶段,Apache首要职业是拍卖客商的劳动需要,在此个阶段Apache以普通客户运营。主假诺安全性考虑,Apache对HTTP的伏乞可以分成连接、管理和断开连接多个大的品级。

    2、FastCGI

    (1卡塔尔(قطر‎cgi是通用网关接口(Common Gateway IntedfaceState of Qatar,它能够让多少个客商端从网页浏览器向实施在Web服务器上的次序央求数据。CGI描述了顾客端和那个顺序之间传输数据的行业内部。CGI的几个指标是独自于任何语言,所以CGI能够用别样语言编写,只要这种语言具备标准输入、输出和意况变量。如PHP、perl、tcl等。

    法斯特CGI是Web服务器和管理程序之间通讯的一种公约,是CGI的一种改革方案,FastCGI疑似二个常驻型的CGI,它能够间接施行,在倡议达到时不会花销时间去fork二个历程来拍卖(那是CGI对位人诟病的fork-and-execute格局State of Qatar。就是因为它只是叁个通讯左券,它还扶植遍及式的运算,即法斯特CGI程序可以在网址服务器以外的主机上施行并且接受来自其余网站服务器的伸手

    法斯特CGI的上上下下工艺流程是这么的:

      Step1:Web Server运转时载入FastCGI进程微电脑(IIS ISAPI或Apache Module卡塔尔

      Step2:法斯特CGI进度微处理机本身最早化,运维多少个CGI解释器进程(可以知道八个php-cgi卡塔尔(قطر‎并等候来自web server的连天

      Step3:当顾客端诉求达到Web Server时,法斯特CGI进度微处理机选取并连接到三个CGI解释器。Web Server将CGI情状变量和专门的学问输入发送到法斯特CGI子进度php-cgi

      Step4:法斯特CGI子进度完结管理后将标准输出和谬误新词从同三番五次接重临Web Server 当法斯特CGI子进程关闭连接时,央浼便截至。法斯特CGI子进程接着等待并管理来自FastCGI进程微处理机(运行在Web Server中State of Qatar的下七个延续。在CGI情势中,php-cgi在此便退出了。

     

    (2)php中CGI实现

    PHP的CGI实现了法斯特cgi合同。是二个TCP或UDP协议的服务器接纳来自Web服务器的乞请,当运行时创设TCP/UDP公约的服务器的socket监听,并选用相关央浼并扩充管理。随后就进来了PHP的生命周期:模块初叶化,sapi开头化,管理PHP央求,模块关闭,sapi关闭等 就结成了全体CGI的生命周期。

    以TCP为例在,在TCP的服务端,日常会实践那样多少个步骤:

    1、调用socket函数创制一个TCP用的流式套接字;

    2、调用bind函数将服务器的本地地址与前面创立的套接字绑定;

    3、调用listen函数将新创立的套接字作为监听,等待顾客端发起的连年,当客商端有多个延续连接到那些套接字时,大概需求排队管理;

    4、服务器进度调用accept函数走入拥塞状态,直到有客商进程调用connect函数而创建起叁个总是;

    5、当与客商端成立连接后,服务器调用read_stream函数读取客商端的倡议;

    6、管理完数据后,服务器调用write函数向客户端发送应答

    TCP上客商-服务器业务的时序如图所示:

    图片 5

    php的CGI实现从cgi_main.c文件的main函数起头,在main函数中调用了概念在fastcgi.c文件中的初阶化,监听等函数。相比TCP的流水生产线,大家查阅php对TCP合同的落到实处,就算php本身也促成了那一个流程,可是在main函数中部分进度被封装成三个函数完成。对应TCP的操作流程,PHP首先会施行创造socket,绑定套接字,创立监听:

    if (bindpath) {
        fcgi_fd = fcgi_listen(bindpath, 128);   //  socket˥˦2sfcgi_initɩ    
    Ȑ
        ...
    }
    

    在fastcgi.c文件中,fcig_listen函数首要用以成立、绑定socket并起头监听,它走完了前头所列TCP流程的前八个级次,

     if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 ||
            ...
            bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 ||
            listen(listen_socket, backlog) < 0) {
            ...
        }
    

    当服务端初叶化完结后,进度调用accept函数进入拥塞状态,在main函数中我们看看如下代码:

      while (parent) {
            do {
                pid = fork();   //  oҟ
    ȨėJ
                switch (pid) {
                case 0: //  ȨėJ
                    parent = 0;
    
                    /* don't catch our signals */
                    sigaction(SIGTERM, &old_term, 0);   //  ľâ¯ķ
                    sigaction(SIGQUIT, &old_quit, 0);   //  ľĿɰ£ƺ
                    sigaction(SIGINT,  &old_int,  0);   //  ľĿKȠƺ
                    break;
                    ...
                    default:
                    /* Fine */
                    running++;
                    break;
            } while (parent && (running < children));
    
        ...
            while (!fastcgi || fcgi_accept_request(&request) >= 0) {
            SG(server_context) = (void *) &request;
            init_request_info(TSRMLS_C);
            CG(interactive) = 0;
                        ...
                }
    

    如上的代码是三个生成子进度,并等候客户央求。在fcgi_accept_request函数中,程序会调用accept函数拥塞新创设的线程。当客户的伸手达到时,fcgi_accept_request函数会剖断是还是不是管理顾客的伸手,个中会过滤有些连接央浼,忽视受节制客商的央浼,如若程序受理顾客的伏乞,他将深入分析央浼的信息,将有关的变量写到对应的变量中。在那之中在读取乞请内容时调用了safe_read方法。如下所示:main(卡塔尔(قطر‎->fcgi_accept_request()->fcgi_read_request()->safe_read()

    static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t 
    count)
    {
        size_t n = 0;
        do {
        ... //  省略 对win32的处理
            ret = read(req->fd, ((char*)buf)+n, count-n);   //  非win版本的读操作
    D‰
        ... // 省略
        } while (n != count);
    
    }
    

    如上相应服务器端读取顾客的央求数据。

    在伸手起先化完毕,读取央浼达成后,就该管理乞求的PHP文件了。即使本次伏乞为PHP_MODE_STANDAMuranoD则会调用php_execute_script实践PHP文件。在那函数中它先最初化此文件有关的部分内容,然后再调用zend_execute_scripts函数,对PHP文件实行词法分析和语法分析,生成人中学间代码,并试行zend_execute函数,进而施行这一个中间代码。

      在拍卖完顾客的乞求后,服务端将回到音信给顾客端,那个时候在main函数中调用的是fcgi_finish_request(&request , 1);fcgi_finish_request函数定义在fasftcgi.c文件中。

      在发送了乞求的答应后,服务器端将会试行关闭操作,只限于CGI自个儿的闭馆,程序实施的是fcgi_close函数。

    本文由威尼斯手机平台登陆发布于前端资源,转载请注明出处:不得不说传统的 CGI,这些内置实现的物理位置在PHP源码的SAPI目录

    关键词: