浓郁理解 FastCGI 左券以致在 PHP 中的完毕

在座谈 法斯特CGI 以前,不能不说守旧的 CGI
的做事原理,同期应当大概理解 CGI
1.1 协议

目录

守旧 CGI 专门的工作原理解析

客户端访谈某些 UHighlanderL 地址然后,通过 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 程序。前边有相比详细的代码解读。

  • 介绍
  • 深入CGI协议
    • CGI的运行规律
    • CGI商谈的毛病
  • 深入FastCGI协议
    • 法斯特CGI左券运营规律
    • 为啥是 法斯特CGI 而非 CGI 契约
    • CGI 与 FastCGI 架构
    • 再看 FastCGI 协议
    • Web 服务器和 法斯特CGI 交互作用进度
    • 干什么要求在新闻头发送 RequestID 那一个标记?
  • PHP-FPM

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 OK\r\nContent-Type:text/html\r\nContent-Length: %d\r\nServer: mengkang\r\n\r\n%s";

    sprintf(res,html_response_template,strlen(buf),buf);

    return res;
}

介绍

在用PHP开采的经过中,大家经常使用Nginx只怕Apache作为大家的Web服务器。可是PHP是怎么样与那一个Web服务器通讯的啊?

  • Apache把PHP作为多个模块集成到Apache进度运转,这种mod_php的运维方式与PHP-CGI未有其余关联。

  • Nginx是通过FastCGI来贯彻与PHP的通讯。

要谈法斯特CGI就务须先说说CGI。那怎么是CGI?

CGI(Common Gateway Interface:通用网关接口State of Qatar是Web
服务器运维时外界程序的正经八百,按CGI 编写的顺序能够扩大服务器功效。CGI
应用程序能与浏览器实行交互作用,还可因此数据库API
与数据库服务器等外界数据源举办通讯,从数据库服务器中获取数据。–百度百科

CGI协议同 HTTP 合同同样是八个「应用层」协议,它的 作用 是为着化解 Web
服务器与 PHP 应用(或其余 Web 应用)之间的通讯难题。

既然如此它是八个「合同」,换言之它与语言非亲非故,即若是是贯彻类 CGI
合同的应用就可以知道落到实处相互影响的通信。

如上代码中的重点:

  • 66~81行找到CGI程序的相对路线(大家为了轻松,间接将其根目录定义为Web程序的当前目录),那样就可以在子进度中推行CGI 程序了;同不经常间设置情状变量,方便CGI程序运转时读取;
  • 94~95行将 CGI 程序的科公输子出结果写入 Web 服务器守护进度的缓存中;
  • 97行则将包裹后的 html 结果写入客商端 socket
    描述符,重返给连接Web服务器的顾客端。

深入CGI协议

我们早就明白了 CGI 左券是为了做到 Web
服务器和采取之间开展多少通讯那些题目。那么,那后生可畏节大家就来探视毕竟它们中间是哪些进展通讯的。

简单的说来说 CGI 合同它呈报了 Web
服务器和应用程序之间开展多少传输的格式,而且只要大家的编制程序语言帮助规范输入、规范输出甚至境遇变量等管理,你就能够使用它来编排一个CGI 程序。

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的运作规律

  • 当客商访问大家的 Web 应用时,会倡导二个 HTTP 央浼。最后 Web
    服务器收到到这几个伏乞。

  • Web 服务器创立三个新的 CGI 进程。在这里个进度中,将 HTTP
    诉求数据已无可反驳格式分析出来,并经过标准输入和遭遇变量传入到 UGL450L
    钦定的 CGI 程序(PHP 应用 $_SERVER)。

  • Web 应用程序管理到位后将回到数据写入到正规输出中,Web
    服务器进度则从正规输出流中读取到响应,并应用 HTTP
    合同再次来到给客商响应。

一句话就是 Web 服务器中的 CGI 进度将选用到的 HTTP
乞请数据读取到情状变量中,通过标准输入转载给 PHP 的 CGI 程序;当 PHP
程序管理落成后,Web 服务器中的 CGI
进度从行业内部输出中读取再次来到数据,并改换回 HTTP
响应音信格式,最后将页面呈献给客户。然后 Web 服务器关闭掉这么些 CGI 进度。

能够说 CGI 公约极度长于管理 Web 服务器和 Web
应用的通讯难题。但是,它有叁个严重破绽,对于各类诉求都急需重新 fork
出一个 CGI 进度,管理到位后立马关闭。

法斯特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 通信,通过 法斯特CGI
    合计算与发放送 CGI 情状变量和职业输入数据给 CGI 解释器进程。
  3. CGI 解释器进度完成管理后将正式输出和错误音讯从同连续接再次回到 Web
    Server。
  4. CGI 解释器进程接着等待并拍卖来自 Web Server 的下贰个连接。

图片 2

法斯特CGI 与古板 CGI 格局的界别之一则是 Web 服务器不是直接施行 CGI
程序了,而是通过 socket 与 法斯特CGI 响应器(法斯特CGI
进程微处理机)实行相互,Web 服务器需求将 CGI 接口数据封装在遵纪守法 法斯特CGI
协议包中发送给 法斯特CGI 响应器程序。就是出于 法斯特CGI 进程微处理器是依据socket 通讯的,所以也是布满式的,Web服务器和CGI响应器服务器分开计划。

再啰嗦一句,FastCGI
是后生可畏种协议,它是树立在CGI/1.1底子之上的,把CGI/1.1里面包车型地铁要传送的数码通过法斯特CGI合同定义的顺序、格式进行传递。

CGI协商的毛病

  • 历次处理客商央浼,都亟需再行 fork CGI 子进程、销毁 CGI 子进度。

  • 风流倜傥层层的 I/O
    开支减少了互联网的吞吐量,酿成了财富的浪费,在大并发时会时有爆发严重的质量难题。

未雨防患于未然准备粮草先行专门的学问

或是上边的剧情掌握起来依旧很肤浅,那是由于第大器晚成对法斯特CGI左券尚未曾一个光景的认知,第二未曾实际代码的学习。所以需求事情发生此前学习下
法斯特CGI
协议的开始和结果,不肯定供给完全看懂,可大致通晓之后,看完本篇再组成着读书驾驭消食。

http://www.fastcgi.com/devkit… (土耳其语原版)
http://andylin02.iteye.com/bl… (中文版)

深入FastCGI协议

从效果与利益上来说,CGI 公约已经完全能够减轻 Web 服务器与 Web
应用之间的多寡通讯难点。不过出于每一个须要都亟需重新 fork 出 CGI
子进程引致质量堪忧,所以基于 CGI 公约的幼功上做了校勘便有了 FastCGI
左券,它是朝气蓬勃种常驻型的 CGI 公约。

精气神儿上来将 法斯特CGI 和 CGI 契约差不离全盘黄金时代致,它们都足以从 Web
服务器里收到到平等的多寡,分化之处在于接受了区别的通讯格局。

再来回想一下 CGI 合同每一回收到到 HTTP 诉求时,都亟需涉世 fork 出 CGI
子过程、施行拍卖并销毁 CGI 子进度这意气风发层层职业。

FastCGI 左券使用 进程间通讯
来处理客商的伸手,上边我们就来看看它的周转规律。

法斯特CGI 左券深入分析

上面结合 PHP 的 法斯特CGI 的代码举行剖释,不作特殊说明以下代码均源于于 PHP
源码。

法斯特CGI合同运营原理

  • 法斯特CGI 进度微型机运维时会成立一个 主 进度和几个 CGI
    解释器进度(Worker 进度),然后等待 Web 服务器的三番若干次。

  • Web 服务器收到 HTTP 央求后,将 CGI 报文通过 套接字(UNIX 或 TCP
    Socket)实行通信,将情状变量和伸手数据写入标准输入,转载到 CGI
    解释器进程。

  • CGI 解释器进程完结管理后将正式输出和错误新闻从同一而再三番若干次接重临给 Web
    服务器。

  • CGI 解释器进度等待下二个 HTTP 央求的到来。

法斯特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;

何以是 法斯特CGI 而非 CGI 左券

借使单单因为做事形式的比不上,就好像并从未什么样大不断的。并没到非要接纳法斯特CGI 合同不可的地步。

而是,对于这几个看似微小的异样,但意义杰出,最后的结果是完结出来的 Web
应用构造上的差别。

新闻的出殡顺序

下图是二个简单的音信传递流程

图片 3

第一发送的是FCGI_BEGIN_REQUEST,然后是FCGI_PARAMSFCGI_STDIN,由于各种消息头(上面将详细表明)里面能够传承的最大尺寸是65535,所以这两种类型的音讯不确定只发送三遍,有异常的大希望三番五次发送数十次。

法斯特CGI
响应体管理完成之后,将发送FCGI_STDOUTFCGI_STDERR,同理也恐怕数次连接发送。最后以FCGI_END_REQUEST意味着乞求的了断。

亟需静心的有些,FCGI_BEGIN_REQUESTFCGI_END_REQUEST个别标志着央求的上三保太监得了,与任何左券城门失火,所以她们的音讯体的内容也是协商的少年老成局地,因而也是有照拂的布局体与之对应(后边会详细表明)。而情形变量、规范输入、标准输出、错误输出,这么些都以职业相关,与谈判毫不相关,所以他们的新闻体的源委则无布局体对应。

鉴于整个新闻是二进制三翻五次传递的,所以必得定义二个合并的组织的消息头,那样以便读取每种音信的音讯体,方便音讯的切割。那在网络通信中是特别布满的意气风发种手腕。

CGI 与 FastCGI 架构

在 CGI 合计中,Web 应用的生命周期完全正视于 HTTP 央浼的表明周期。

对各样选取到的 HTTP 央浼,都亟待重启二个 CGI
进度来张开处理,管理到位后必需关闭 CGI 进度,技艺完毕公告 Web 服务器此番HTTP 央求管理完了的指标。

但是在 法斯特CGI 中全然不类似。

法斯特CGI 进度是常驻型的,大器晚成旦运营就足以管理全数的 HTTP
央求,而不需求直接退出。

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标志记录所属的FastCGI央求。
  • 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("%d\n", (requestIdB1 << 8) + requestIdB0); // 65535
}

您也许会想到借使二个音信体长度超过65535如何是好,则分割为五个肖似档案的次序的新闻发送就可以。

再看 FastCGI 协议

经过前边的授课,大家相比较已经能够很准确的讲出来 FastCGI
是风姿洒脱种通信公约

那样的结论。今后,大家就将关心的核心挪到和煦自己,来探望那么些左券的定义。

同 HTTP 公约同样,法斯特CGI 左券也会有新闻头和新闻体组成。

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服务器为线路保持响应性。

音讯头音信

着重的消息头音讯如下:

  • Version: 用于表示 法斯特CGI 左券版本号。

  • Type: 用于标记 法斯特CGI 消息的类别 – 用于钦赐管理那些新闻的点子。

  • RequestID: 标记出当下所属的 法斯特CGI 央浼。

  • Content Length: 数据手袋体所占字节数。

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_OVEMuranoLOADED:拒却新央浼。那爆发在应用用完有些能源时,举个例子数据库连接。
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 自定义的。

音信类型定义

  • BEGIN_REQUEST: 从 Web 服务器发送到 Web
    应用,表示在此之前拍卖新的央浼。

  • ABORT_REQUEST: 从 Web 服务器发送到 Web
    应用,表示暂停二个拍卖中的诉求。比如,客户在浏览器发起倡议后按下浏览器上的「甘休开关」时,会接触这一个音讯。

  • END_REQUEST: 从 Web 应用发送给 Web
    服务器,表示该央求管理完了。再次回到数据包里富含「再次回到的代码」,它决定伏乞是还是不是中标拍卖。

  • PARAMS: 「流数据包」,从 Web 服务器发送到 Web
    应用。这时能够发送多个数据包。发送截至标志为从 Web
    服务器发出叁个长度为 0 的空包。且 PARAMS 中的数据类型和 CGI
    左券生机勃勃致。即大家选取 $_SE昂科雷VE福特Explorer 获取到的系统际遇等。

  • STDIN: 「流数据包」,用于 Web 应用从正规输入中读抽取顾客提交的
    POST 数据。

  • STDOUT: 「流数据报」,从 Web
    应用写入到正式输出中,满含再次回到给客户的数量。

消息报导样例

为了轻易的代表,音信头只展示音信的品种和音讯的
id,其他字段都不给与体现。上面包车型客车例子来自于官方网站

{FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS,          1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{FCGI_STDIN,           1, "quantity=100&item=3047936"}
{FCGI_STDOUT,          1, "Content-type: text/html\r\n\r\n<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 服务器深入剖析。

Web 服务器和 FastCGI 人机联作进程

  • Web 服务器收到客商央求,但最后管理央浼由 Web 应用实现。那个时候,Web
    服务器尝试通过套接字(UNIX 或 TCP 套接字,具体运用哪个由 Web
    服务器配置决定)连接到 法斯特CGI 进度。

  • FastCGI
    进程查看选取到的连年。选用「接纳」或「否决」连接。借使是「接纳」连接,则从正规输入流中读取数据包。

  • 朝气蓬勃经 法斯特CGI
    进程在指准时期内未有得逞接到到连年,则该央求退步。不然,Web
    服务器发送贰个包含唯风华正茂的RequestID 的 BEGIN_REQUEST 类型音讯给到
    法斯特CGI 进度。后续全数数据包发送都饱含那一个 RequestID。 然后,Web
    服务器发送放肆数量的 PARAMS 类型音信到 法斯特CGI
    进度。生机勃勃旦发送完结,Web 服务器通过发送贰个空PARAMS
    音讯包,然后破产这几个流。 其余,假使客户发送了 POST 数据 Web
    服务器会将其写入到 规范输入 发送给 法斯特CGI 进度。当有着 POST
    数据发送完毕,会发送一个空的 规范输入 来关闭那么些流。

  • 並且,法斯特CGI 进度接纳到 BEGINREQUEST 类型数据包。它可以通过响应
    ENDREQUEST
    来拒却那么些央浼。或然采取并管理这一个须求。假如收到央浼,FastCGI
    进程会等待接纳全数的 PARAMS 和 标准输入数据包。
    然后,在拍卖要求并将再次来到结果写入 标准输出
    流。管理完了后,发送贰个空的数码包到规范输出来关闭那些流,而且会发送四个END_REQUEST 类型音讯文告 Web 服务器,告知它是不是发生错误特别。

PHP 中的 FastCGI 的实现

下直面代码的解读笔记只是自身个人文化的一个梳理提炼,如有改善,请大家提出。对不了解该代码的校友来讲可能是一个指引,开始认知,假若以为很模糊不分明,那么照旧要求自身逐行去阅读。

php-src/sapi/cgi/cgi_main.c为例进行剖判表达,假使开拓意况为 unix
情况。main 函数中一些变量的概念,以至 sapi
的初步化,大家就不探究在此研究了,只表明有关 法斯特CGI 相关的开始和结果。

何以要求在新闻头发送 RequestID 这一个标志?

设假设各样连接仅管理一个号召,发送 RequestID 则略显多余。

可是大家的 Web 服务器和 法斯特CGI
进度之间的连接恐怕管理多少个乞求,即叁个连接能够管理四个伏乞。所以才供给运用数据中国包装技协议并非直接行使单个数据流的原因:以促成「多路复用」。

进而,由于每一个数据包都包括唯意气风发的 RequestID,所以 Web
服务器工夫在一个总是上发送大肆数量的央求,况且 法斯特CGI
进度也能够从三个一而再上收到到放肆数量的伸手数据包。

除此以外大家还亟需鲜明一点正是 Web 服务器 与 法斯特CGI 进度间通讯是
严节的。尽管大家在相互作用进度中看起来一个伸手是上行下效的,不过大家的 Web
服务器也可以有超级大可能率在同一时间发出几拾个 BEGIN_REQUEST
类型的数据包,由此及彼。

1.展开一个 socket 监听服务

fcgi_fd = fcgi_listen(bindpath, 128);

从此现在处早前监听,而fcgi_listen函数里面则产生 socket
服务前三步socket,bind,listen

PHP-FPM

PHP-FPM即PHP-FastCGI Process Manager.

PHP-FPM是法斯特CGI的实现,并提供了经过管理的功效。

经过包含 master 进程和 worker 进度三种进度。

master 进度唯有一个,担任监听端口,选择来自 Web Server 的乞求,而 worker
进度则相仿有多少个(具体数额依照实际要求配备卡塔尔国,每一个进度之中都放置了贰个 PHP
解释器,是 PHP 代码真正施行的地点。

PHP-FPM 是 法斯特CGI 进度微处理器(PHP FastCGI Process
Manager)( PHP 内核的
法斯特CGI 的大相当多外加成效(或然说意气风发种替代的 PHP 法斯特CGI
落成),对于高负载网站是特别平价的。

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;

PHP-FPM怎么样工作的?

PHP-FPM 进程微电脑有三种进度组成,二个 Master 进度和多少个 Worker
进度。Master 进程担当监听端口,接收来自 Web 服务器的伸手,然后指派具体的
Worker 进度管理诉求;worker 进程则平日常有多少个(依赖配置决定进度数卡塔尔,每一个进度之中都放置了三个 PHP 解释器,用来实行 PHP
代码。

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));

Nginx 服务器怎么着与 FastCGI 协同职业

Nginx 服务器相当小概直接与 FastCGI 服务器进行通讯,须要启用
ngx_http_fastcgi_module 模块举行代理配置,本事将央求发送给 法斯特CGI
服务。

转载:

PHP和Apache是何许通讯的?

Nginx+PHP-FPM运营原理安详严整

控制CGI和法斯特CGI合同的运维规律

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;

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;
        }

        ...
    }
}

6.施行脚本

若果此次央浼为PHP_MODE_STANDARD则会调用php_execute_script举办PHP文件。这里就不实行了。

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 连接的顾客端描述符。

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 FastCGI
数据包的封装.

fcgi_write(request, FCGI_STDOUT, str, str_length);

写在结尾

把 法斯特CGI
的学问学习明白的进程做了如此生龙活虎篇笔记,把团结掌握的故事情节(自己感觉)有系统地写出来,能够让外人比较便于看明白也是风度翩翩件不挺不易于的事。同时也让本身对这一个知识点的明白又深深了后生可畏层。对
PHP 代码学习领悟中还也可以有超多吸引的地点还须要自己本人早先时期慢慢消化摄取和透亮。

本文都以一德一心的一些精晓,水平有限,如有订正,希望我们赋予指正。

坚定不移看完本的都以老手,说真的,前面有个别太干燥了!即便能把各样知识点真正领会消食,相对受益良多。

相关文章