PHP進階之路 - 深入理解FastCGI協(xié)議以及在PHP中的實現(xiàn)

傳統(tǒng) CGI 工作原理分析

客戶端訪問某個 URL 地址之后讲婚,通過 GET/POST/PUT 等方式提交數據钦无,并通過 HTTP 協(xié)議向 Web 服務器發(fā)出請求,服務器端的 HTTP Daemon(守護進程)將 HTTP 請求里描述的信息通過標準輸入 stdin 和環(huán)境變量(environment variable)傳遞給主頁指定的 CGI 程序,并啟動此應用程序進行處理(包括對數據庫的處理),處理結果通過標準輸出 stdout 返回給 HTTP Daemon 守護進程,再由 HTTP Daemon 進程通過 HTTP 協(xié)議返回給客戶端斤斧。

上面的這段話理解可能還是比較抽象,下面我們就通過一次GET請求為例進行詳細說明霎烙。
1478757541407927.png
image.gif

?
下面用代碼來實現(xiàn)圖中表述的功能撬讽。Web 服務器啟動一個 socket 監(jiān)聽服務,然后在本地執(zhí)行 CGI 程序悬垃。后面有比較詳細的代碼解讀游昼。

10年架構師領你架構-成長之路-(附面試題(含答案))

(騰訊T3-T4)打造互聯(lián)網PHP架構師教程目錄大全,只要你看完尝蠕,薪資立馬提升2倍(持續(xù)更新)

點擊與我交流企鵝群.

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 環(huán)境變量設置演示 ============================

        // 例如 "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 環(huán)境變量設置演示 ============================

        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;
}
image.gif

如上代碼中的重點:

  • 66~81行找到CGI程序的相對路徑(我們?yōu)榱撕唵谓创玻苯訉⑵涓夸浂x為Web程序的當前目錄),這樣就可以在子進程中執(zhí)行 CGI 程序了趟佃;同時設置環(huán)境變量扇谣,方便CGI程序運行時讀让两荨;

  • 94~95行將 CGI 程序的標準輸出結果寫入 Web 服務器守護進程的緩存中罐寨;

  • 97行則將包裝后的 html 結果寫入客戶端 socket 描述符靡挥,返回給連接Web服務器的客戶端。

  • 感謝大家一直來支持鸯绿,這是我準備的1000粉絲福利

    【1000粉絲福利】10年架構師分享PHP進階架構資料跋破,助力大家都能30K

    點擊與我交流企鵝群.

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;
}
image.gif

將上面的 CGI 程序編譯成gcc user.c -o user.cgi,放在上面web程序的同級目錄瓶蝴。
代碼中的第28行毒返,從環(huán)境變量中讀取前面在Web服務器守護進程中設置的環(huán)境變量,是我們演示的重點舷手。

FastCGI 工作原理分析

相對于 CGI/1.1 規(guī)范在 Web 服務器在本地 fork 一個子進程執(zhí)行 CGI 程序拧簸,填充 CGI 預定義的環(huán)境變量,放入系統(tǒng)環(huán)境變量男窟,把 HTTP body 體的 content 通過標準輸入傳入子進程盆赤,處理完畢之后通過標準輸出返回給 Web 服務器。FastCGI 的核心則是取締傳統(tǒng)的 fork-and-execute 方式歉眷,減少每次啟動的巨大開銷(后面以 PHP 為例說明)牺六,以常駐的方式來處理請求。

FastCGI 工作流程如下:

  1. FastCGI 進程管理器自身初始化汗捡,啟動多個 CGI 解釋器進程淑际,并等待來自 Web Server 的連接。
  1. Web 服務器與 FastCGI 進程管理器進行 Socket 通信扇住,通過 FastCGI 協(xié)議發(fā)送 CGI 環(huán)境變量和標準輸入數據給 CGI 解釋器進程庸追。
  1. CGI 解釋器進程完成處理后將標準輸出和錯誤信息從同一連接返回 Web Server。
  1. CGI 解釋器進程接著等待并處理來自 Web Server 的下一個連接台囱。
1478757572486219.png
image.gif

?

FastCGI 與傳統(tǒng) CGI 模式的區(qū)別之一則是 Web 服務器不是直接執(zhí)行 CGI 程序了,而是通過 socket 與 FastCGI 響應器(FastCGI 進程管理器)進行交互读整,Web 服務器需要將 CGI 接口數據封裝在遵循 FastCGI 協(xié)議包中發(fā)送給 FastCGI 響應器程序簿训。正是由于 FastCGI 進程管理器是基于 socket 通信的,所以也是分布式的米间,Web服務器和CGI響應器服務器分開部署强品。

再啰嗦一句,F(xiàn)astCGI 是一種協(xié)議屈糊,它是建立在CGI/1.1基礎之上的的榛,把CGI/1.1里面的要傳遞的數據通過FastCGI協(xié)議定義的順序、格式進行傳遞逻锐。

準備工作

可能上面的內容理解起來還是很抽象夫晌,這是由于第一對FastCGI協(xié)議還沒有一個大概的認識雕薪,第二沒有實際代碼的學習。所以需要預先學習下 FastCGI 協(xié)議的內容晓淀,不一定需要完全看懂所袁,可大致了解之后,看完本篇再結合著學習理解消化凶掰。

http://www.fastcgi.com/devkit... (英文原版)
http://andylin02.iteye.com/bl... (中文版)

FastCGI 協(xié)議分析

下面結合 PHP 的 FastCGI 的代碼進行分析燥爷,不作特殊說明以下代碼均來自于 PHP 源碼。

FastCGI 消息類型

FastCGI 將傳輸的消息做了很多類型的劃分懦窘,其結構體定義如下:

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;
image.gif

消息的發(fā)送順序

下圖是一個簡單的消息傳遞流程

1478757581275137.png
image.gif

?

最先發(fā)送的是FCGI_BEGIN_REQUEST前翎,然后是FCGI_PARAMSFCGI_STDIN,由于每個消息頭(下面將詳細說明)里面能夠承載的最大長度是65535畅涂,所以這兩種類型的消息不一定只發(fā)送一次港华,有可能連續(xù)發(fā)送多次。

FastCGI 響應體處理完畢之后毅戈,將發(fā)送FCGI_STDOUT苹丸、FCGI_STDERR,同理也可能多次連續(xù)發(fā)送苇经。最后以FCGI_END_REQUEST表示請求的結束赘理。

需要注意的一點,FCGI_BEGIN_REQUESTFCGI_END_REQUEST分別標識著請求的開始和結束扇单,與整個協(xié)議息息相關商模,所以他們的消息體的內容也是協(xié)議的一部分,因此也會有相應的結構體與之對應(后面會詳細說明)蜘澜。而環(huán)境變量施流、標準輸入、標準輸出鄙信、錯誤輸出瞪醋,這些都是業(yè)務相關,與協(xié)議無關装诡,所以他們的消息體的內容則無結構體對應银受。

由于整個消息是二進制連續(xù)傳遞的,所以必須定義一個統(tǒng)一的結構的消息頭鸦采,這樣以便讀取每個消息的消息體宾巍,方便消息的切割。這在網絡通訊中是非常常見的一種手段渔伯。

FastCGI 消息頭

如上顶霞,F(xiàn)astCGI 消息分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;
image.gif

字段解釋下:
version標識FastCGI協(xié)議版本蓝厌。
type 標識FastCGI記錄類型,也就是記錄執(zhí)行的一般職能鲜侥。
requestId標識記錄所屬的FastCGI請求褂始。
contentLength記錄的contentData組件的字節(jié)數。
關于上面的xxB1xxB0的協(xié)議說明:當兩個相鄰的結構組件除了后綴“B1”和“B0”之外命名相同時描函,它表示這兩個組件可視為估值為B1<<8 + B0的單個數字崎苗。該單個數字的名字是這些組件減去后綴的名字。這個約定歸納了一個由超過兩個字節(jié)表示的數字的處理方式舀寓。

比如協(xié)議頭中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
}
image.gif

你可能會想到如果一個消息體長度超過65535怎么辦胆数,則分割為多個相同類型的消息發(fā)送即可。

FCGI_BEGIN_REQUEST 的定義

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

字段解釋

role表示Web服務器期望應用扮演的角色互墓。分為三個角色(而我們這里討論的情況一般都是響應器角色)

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

FCGI_BEGIN_REQUEST中的flags組件包含一個控制線路關閉的位:flags & FCGI_KEEP_CONN:如果為0必尼,則應用在對本次請求響應后關閉線路。如果非0篡撵,應用在對本次請求響應后不會關閉線路判莉;Web服務器為線路保持響應性。

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;
image.gif

大廠2000道面試題(含答案)

PHP面試題匯總育谬,看完這些面試題助力你面試成功券盅,工資必有20-25K

點擊與我交流企鵝群.

字段解釋
appStatus組件是應用級別的狀態(tài)碼。
protocolStatus組件是協(xié)議級別的狀態(tài)碼膛檀;protocolStatus的值可能是:

FCGI_REQUEST_COMPLETE:請求的正常結束锰镀。
FCGI_CANT_MPX_CONN:拒絕新請求。這發(fā)生在Web服務器通過一條線路向應用發(fā)送并發(fā)的請求時咖刃,后者被設計為每條線路每次處理一個請求泳炉。
FCGI_OVERLOADED:拒絕新請求。這發(fā)生在應用用完某些資源時嚎杨,例如數據庫連接花鹅。
FCGI_UNKNOWN_ROLE:拒絕新請求。這發(fā)生在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;
image.gif

需要注意dcgi_protocol_statusfcgi_role各個元素的值都是 FastCGI 協(xié)議里定義好的刨肃,而非 PHP 自定義的。

消息通訊樣例

為了簡單的表示自脯,消息頭只顯示消息的類型和消息的 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}}
image.gif

配合上面各個結構體膏潮,則可以大致想到 FastCGI 響應器的解析和響應流程:

首先讀取消息頭,得到其類型為FCGI_BEGIN_REQUEST满力,然后解析其消息體焕参,得知其需要的角色就是FCGI_RESPONDER轻纪,flag為0,表示請求結束后關閉線路叠纷。然后解析第二段消息刻帚,得知其消息類型為FCGI_PARAMS,然后直接將消息體里的內容以回車符切割后存入環(huán)境變量涩嚣。與之類似崇众,處理完畢之后,則返回了FCGI_STDOUT消息體和FCGI_END_REQUEST消息體供 Web 服務器解析航厚。

PHP 中的 FastCGI 的實現(xiàn)

下面對代碼的解讀筆記只是我個人知識的一個梳理提煉顷歌,如有勘誤,請大家指出幔睬。對不熟悉該代碼的同學來說可能是一個引導眯漩,初步認識,如果覺得很模糊不清晰麻顶,那么還是需要自己逐行去閱讀赦抖。

php-src/sapi/cgi/cgi_main.c為例進行分析說明,假設開發(fā)環(huán)境為 unix 環(huán)境辅肾。main 函數中一些變量的定義队萤,以及 sapi 的初始化,我們就不討論在這里討論了宛瞄,只說明關于 FastCGI 相關的內容浮禾。

1.開啟一個 socket 監(jiān)聽服務

fcgi_fd = fcgi_listen(bindpath, 128);
image.gif

從這里開始監(jiān)聽,而fcgi_listen函數里面則完成 socket 服務前三步socket,bind,listen份汗。

2.初始化請求對象

fcgi_request對象分配內存盈电,綁定監(jiān)聽的 socket 套接字。

fcgi_init_request(&request, fcgi_fd);
image.gif

整個請求從輸入到返回杯活,都圍繞著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;
image.gif

3.創(chuàng)建多個 CGI 解析器子進程

這里子進程的個數默認是0,從配置文件中讀取設置到環(huán)境變量旁钧,然后在程序中讀取吸重,然后創(chuàng)建指定數目的子進程來等待處理 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歪今,防止循環(huán) 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));
image.gif

4.在子進程中接收請求

到這里一切都還是 socket 的服務的套路嚎幸。接受請求,然后調用了fcgi_read_request寄猩。

fcgi_accept_request(&request)
image.gif
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;
    }
}
image.gif

并且把request放入全局變量sapi_globals.server_context,這點很重要,方便了在其他地方對請求的調用替废。

SG(server_context) = (void *) &request;
image.gif

5.讀取數據

下面的代碼刪除一些異常情況的處理箍铭,只顯示了正常情況下執(zhí)行順序。
fcgi_read_request中則完成我們在消息通訊樣例中的消息讀取椎镣,而其中很多的len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0;操作诈火,已經在前面的FastCGI 消息頭中解釋過了。
這里是解析 FastCGI 協(xié)議的關鍵状答。

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;
}
image.gif
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;
        }

        ...
    }
}
image.gif

6.執(zhí)行腳本

假設此次請求為PHP_MODE_STANDARD則會調用php_execute_script執(zhí)行PHP文件冷守。這里就不展開了。

7.結束請求

fcgi_finish_request(&request, 1);
image.gif
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;
}
image.gif

fcgi_finish_request中調用fcgi_flush剪况,fcgi_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;
}
image.gif

fcgi_read中則對FCGI_STDIN的數據進行讀取淮菠。
同時cgi_sapi_module中定義了sapi_cgibin_ub_write來接管輸出處理,而其中又調用了sapi_cgibin_single_write荤堪,最后實現(xiàn)了FCGI_STDOUT FastCGI 數據包的封裝.

fcgi_write(request, FCGI_STDOUT, str, str_length);
image.gif

寫在最后

把 FastCGI 的知識學習理解的過程做了這樣一篇筆記合陵,把自己理解的內容(自我認為)有條理地寫出來,能夠讓別人比較容易看明白也是一件不挺不容易的事澄阳。同時也讓自己對這個知識點的理解又深入了一層拥知。對 PHP 代碼學習理解中還有很多困惑的地方還需要我自己后期慢慢消化和理解。

本文都是自己的一些理解碎赢,水平有限低剔,如有勘誤,希望大家予以指正肮塞。

喜歡我的文章就關注我吧襟齿,持續(xù)更新中.....

以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸枕赵,業(yè)務代碼寫多了沒有方向感猜欺,不知道該從那里入手去提升,對此我整理了一些資料拷窜,包括但不限于:分布式架構开皿、高可擴展钓试、高性能、高并發(fā)副瀑、服務器性能調優(yōu)、TP6恋谭,laravel糠睡,YII2,Redis疚颊,Swoole狈孔、Swoft、Kafka材义、Mysql優(yōu)化均抽、shell腳本、Docker其掂、微服務油挥、Nginx等多個知識點高級進階干貨需要的可以免費分享給大家,需要的可以點擊進入暗號:知乎款熬。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末深寥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贤牛,更是在濱河造成了極大的恐慌惋鹅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殉簸,死亡現(xiàn)場離奇詭異闰集,居然都是意外死亡,警方通過查閱死者的電腦和手機般卑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門武鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人椭微,你說我怎么就攤上這事洞坑。” “怎么了蝇率?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵迟杂,是天一觀的道長。 經常有香客問我本慕,道長排拷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任锅尘,我火速辦了婚禮监氢,結果婚禮上布蔗,老公的妹妹穿的比我還像新娘。我一直安慰自己浪腐,他們只是感情好纵揍,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著议街,像睡著了一般泽谨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上特漩,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天吧雹,我揣著相機與錄音,去河邊找鬼涂身。 笑死雄卷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蛤售。 我是一名探鬼主播丁鹉,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼悴能!你這毒婦竟也來了鳄炉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤搜骡,失蹤者是張志新(化名)和其女友劉穎拂盯,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體记靡,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡谈竿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了摸吠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空凸。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寸痢,靈堂內的尸體忽然破棺而出呀洲,到底是詐尸還是另有隱情,我是刑警寧澤啼止,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布道逗,位于F島的核電站,受9級特大地震影響献烦,放射性物質發(fā)生泄漏滓窍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一巩那、第九天 我趴在偏房一處隱蔽的房頂上張望吏夯。 院中可真熱鬧此蜈,春花似錦、人聲如沸噪生。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跺嗽。三九已至顾瞪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抛蚁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工惕橙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞧甩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓弥鹦,卻偏偏與公主長得像肚逸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子彬坏,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353