澳门新浦京电子游戏深入理解PHP内核(三)概览-SAPI概述

在商量 法斯特CGI 在此以前,必须要说古板的 CGI
的办事规律,同期应该大致领会 CGI
1.1 协议

 本文链接:

历史观 CGI 工作规律分析

顾客端访问有些 U凯雷德L 地址然后,通过 GET/POST/PUT 等格局交给数据,并通过
HTTP 公约向 Web 服务器发出央浼,服务器端的 HTTP Daemon(守护进程)将
HTTP 央浼里描述的音信透过标准输入 stdin 和境况变量(environment
variableState of Qatar传递给主页内定的 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
服务器。FastCGI 的中坚则是不允许古板的 fork-and-execute
方式,收缩每一次运营的壮烈花销(后边以 PHP
为例表明),以常驻的主意来管理乞请。

法斯特CGI 职业流程如下:

  1. 法斯特CGI 进度微处理器本身开首化,运转七个 CGI 解释器进程,并等候来自
    Web Server 的接连。
  2. Web 服务器与 法斯特CGI 进度管理器举办 Socket 通讯,通过 法斯特CGI
    议和发送 CGI 境况变量和规范输入数据给 CGI 解释器进度。
  3. CGI 解释器进程完结管理后将业内输出和错误消息从同一而再接再次回到 Web
    Server。
  4. CGI 解释器进度接着等待并管理来自 Web Server 的下贰个老是。

澳门新浦京电子游戏 3

法斯特CGI 与价值观 CGI 形式的差距之一则是 Web 服务器不是直接实行 CGI
程序了,而是经过 socket 与 法斯特CGI 响应器(法斯特CGI
进程微处理器)进行互动,Web 服务器必要将 CGI 接口数据封装在遵照 法斯特CGI
左券包中发送给 法斯特CGI 响应器程序。就是由于 法斯特CGI 进度微处理机是基于
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标记FastCGI公约版本。
  • 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_OVE本田CR-VLOADED:谢绝新央求。那产生在动用用完有些能源时,举例数据库连接。
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逐风流倜傥要素的值都以 FastCGI
左券里定义好的,而非 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属性在最终是透过宏STANDA奥迪Q5D20_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_module卡塔尔将模块激活,此处的激活正是将模块放入相应的链表中(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陆风X8D20_MODULE_STUFF的原因,那几个宏它满含了前边8个字段的概念。STANDA宝马X5D20_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中调用读取库克ie时,最后得到的数码的职位是在激活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构造的说辞:统后生可畏接口,面向接口编制程序,具备更加好的扩张性和适应性。

(2State of QatarApache的运作进程

Apache的运营蕴含运行阶段和平运动作阶段,运营阶段Apache以root达成运转,整个进程处于单进度单线程的条件中,这么些阶段包罗布署文件深入分析、模块加载、系统能源开首化(比如日志文件、分享内部存款和储蓄器段、数据库连接等卡塔尔国等职业。

在运维阶段,Apache首要办事是拍卖顾客的服务央浼,在这里个阶段Apache以普通顾客运转。首即使安全性思量,Apache对HTTP的乞请能够分为连接、管理和断开连接八个大的阶段。

2、FastCGI

(1State of Qatarcgi是通用网关接口(Common Gateway
Intedface卡塔尔(قطر‎,它能够让一个顾客端从网页浏览器向施行在Web服务器上的主次须要数据。CGI描述了顾客端和那么些程序之间传输数据的正统。CGI的叁个目标是单独于此外语言,所以CGI能够用别的语言编写,只要这种语言具备标准输入、输出和意况变量。如PHP、perl、tcl等。

法斯特CGI是Web服务器和管理程序之间通讯的生龙活虎种左券,是CGI的风流洒脱种改正方案,法斯特CGI疑似一个常驻型的CGI,它能够一直进行,在倡议达到时不会花销时间去fork叁个进度来管理(那是CGI对位人诟病的fork-and-execute情势State of Qatar。便是因为它只是二个通讯公约,它还辅助分布式的运算,即法斯特CGI程序能够在网址服务器以外的主机上推行何况选用来自其余网址服务器的央浼

法斯特CGI的全方位工艺流程是这么的:

  Step1:Web Server运营时载入法斯特CGI进程微电脑(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子进程接着等待并管理来自法斯特CGI进度微处理机(运维在Web
Server中State of Qatar的下一个三回九转。在CGI格局中,php-cgi在那便退出了。

 

(2)php中CGI实现

PHP的CGI达成了Fastcgi公约。是八个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_STANDAEscortD则会调用php_execute_script推行PHP文件。在那函数中它先开端化此文件有关的有的内容,然后再调用zend_execute_scripts函数,对PHP文件实行词法分析和语法解析,生成人中学间代码,并试行zend_execute函数,进而实行那么些中间代码。

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

  在出殡和安葬了央求的对答后,服务器端将会施行关闭操作,仅限于CGI自身的关门,程序实行的是fcgi_close函数。

发表评论

电子邮件地址不会被公开。 必填项已用*标注