天天看點

PHP進階之路 - 深入了解FastCGI協定以及在PHP中的實作傳統 CGI 工作原理分析FastCGI 工作原理分析PHP 中的 FastCGI 的實作寫在最後

傳統 CGI 工作原理分析

用戶端通路某個 URL 位址之後,通過 GET/POST/PUT 等方式送出資料,并通過 HTTP 協定向 Web 伺服器送出請求,伺服器端的 HTTP Daemon(守護程序)将 HTTP 請求裡描述的資訊通過标準輸入 stdin 和環境變量(environment variable)傳遞給首頁指定的 CGI 程式,并啟動此應用程式進行處理(包括對資料庫的處理),處理結果通過标準輸出 stdout 傳回給 HTTP Daemon 守護程序,再由 HTTP Daemon 程序通過 HTTP 協定傳回給用戶端。

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

PHP進階之路 - 深入了解FastCGI協定以及在PHP中的實作傳統 CGI 工作原理分析FastCGI 工作原理分析PHP 中的 FastCGI 的實作寫在最後

下面用代碼來實作圖中表述的功能。Web 伺服器啟動一個 socket 監聽服務,然後在本地執行 CGI 程式。後面有比較詳細的代碼解讀。

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

(騰訊T3-T4)打造網際網路PHP架構師教程目錄大全,隻要你看完,薪資立馬提升2倍(持續更新)

點選與我交流企鵝群.

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

如上代碼中的重點:

  • 66~81行找到CGI程式的相對路徑(我們為了簡單,直接将其根目錄定義為Web程式的目前目錄),這樣就可以在子程序中執行 CGI 程式了;同時設定環境變量,友善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;
}
           

将上面的 CGI 程式編譯成

gcc user.c -o user.cgi

,放在上面web程式的同級目錄。

代碼中的第28行,從環境變量中讀取前面在Web伺服器守護程序中設定的環境變量,是我們示範的重點。

FastCGI 工作原理分析

相對于 CGI/1.1 規範在 Web 伺服器在本地 fork 一個子程序執行 CGI 程式,填充 CGI 預定義的環境變量,放入系統環境變量,把 HTTP body 體的 content 通過标準輸入傳入子程序,處理完畢之後通過标準輸出傳回給 Web 伺服器。FastCGI 的核心則是取締傳統的 fork-and-execute 方式,減少每次啟動的巨大開銷(後面以 PHP 為例說明),以常駐的方式來處理請求。

FastCGI 工作流程如下:

  1. FastCGI 程序管理器自身初始化,啟動多個 CGI 解釋器程序,并等待來自 Web Server 的連接配接。
  2. Web 伺服器與 FastCGI 程序管理器進行 Socket 通信,通過 FastCGI 協定發送 CGI 環境變量和标準輸入資料給 CGI 解釋器程序。
  3. CGI 解釋器程序完成處理後将标準輸出和錯誤資訊從同一連接配接傳回 Web Server。
  4. CGI 解釋器程序接着等待并處理來自 Web Server 的下一個連接配接。
PHP進階之路 - 深入了解FastCGI協定以及在PHP中的實作傳統 CGI 工作原理分析FastCGI 工作原理分析PHP 中的 FastCGI 的實作寫在最後

FastCGI 與傳統 CGI 模式的差別之一則是 Web 伺服器不是直接執行 CGI 程式了,而是通過 socket 與 FastCGI 響應器(FastCGI 程序管理器)進行互動,Web 伺服器需要将 CGI 接口資料封裝在遵循 FastCGI 協定包中發送給 FastCGI 響應器程式。正是由于 FastCGI 程序管理器是基于 socket 通信的,是以也是分布式的,Web伺服器和CGI響應器伺服器分開部署。

再啰嗦一句,FastCGI 是一種協定,它是建立在CGI/1.1基礎之上的,把CGI/1.1裡面的要傳遞的資料通過FastCGI協定定義的順序、格式進行傳遞。

準備工作

可能上面的内容了解起來還是很抽象,這是由于第一對FastCGI協定還沒有一個大概的認識,第二沒有實際代碼的學習。是以需要預先學習下 FastCGI 協定的内容,不一定需要完全看懂,可大緻了解之後,看完本篇再結合着學習了解消化。

http://www.fastcgi.com/devkit... (英文原版)

http://andylin02.iteye.com/bl... (中文版)

FastCGI 協定分析

下面結合 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;
           

消息的發送順序

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

PHP進階之路 - 深入了解FastCGI協定以及在PHP中的實作傳統 CGI 工作原理分析FastCGI 工作原理分析PHP 中的 FastCGI 的實作寫在最後

最先發送的是

FCGI_BEGIN_REQUEST

,然後是

FCGI_PARAMS

FCGI_STDIN

,由于每個消息頭(下面将詳細說明)裡面能夠承載的最大長度是65535,是以這兩種類型的消息不一定隻發送一次,有可能連續發送多次。

FastCGI 響應體處理完畢之後,将發送

FCGI_STDOUT

FCGI_STDERR

,同理也可能多次連續發送。最後以

FCGI_END_REQUEST

表示請求的結束。

需要注意的一點,

FCGI_BEGIN_REQUEST

FCGI_END_REQUEST

分别辨別着請求的開始和結束,與整個協定息息相關,是以他們的消息體的内容也是協定的一部分,是以也會有相應的結構體與之對應(後面會詳細說明)。而環境變量、标準輸入、标準輸出、錯誤輸出,這些都是業務相關,與協定無關,是以他們的消息體的内容則無結構體對應。

由于整個消息是二進制連續傳遞的,是以必須定義一個統一的結構的消息頭,這樣以便讀取每個消息的消息體,友善消息的切割。這在網絡通訊中是非常常見的一種手段。

FastCGI 消息頭

如上,FastCGI 消息分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

 辨別FastCGI記錄類型,也就是記錄執行的一般職能。

requestId

辨別記錄所屬的FastCGI請求。

contentLength

記錄的contentData元件的位元組數。

關于上面的

xxB1

xxB0

的協定說明:當兩個相鄰的結構元件除了字尾“B1”和“B0”之外命名相同時,它表示這兩個元件可視為估值為B1<<8 + B0的單個數字。該單個數字的名字是這些元件減去字尾的名字。這個約定歸納了一個由超過兩個位元組表示的數字的處理方式。

比如協定頭中

requestId

contentLength

表示的最大值就是

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怎麼辦,則分割為多個相同類型的消息發送即可。

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伺服器為線路保持響應性。

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;
           

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

PHP面試題彙總,看完這些面試題助力你面試成功,工資必有20-25K

點選與我交流企鵝群.

字段解釋

appStatus

元件是應用級别的狀态碼。

protocolStatus

元件是協定級别的狀态碼;

protocolStatus

的值可能是:

FCGI_REQUEST_COMPLETE:請求的正常結束。

FCGI_CANT_MPX_CONN:拒絕新請求。這發生在Web伺服器通過一條線路向應用發送并發的請求時,後者被設計為每條線路每次處理一個請求。

FCGI_OVERLOADED:拒絕新請求。這發生在應用用完某些資源時,例如資料庫連接配接。

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_status

fcgi_role

各個元素的值都是 FastCGI 協定裡定義好的,而非 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}}
           

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

首先讀取消息頭,得到其類型為

FCGI_BEGIN_REQUEST

,然後解析其消息體,得知其需要的角色就是

FCGI_RESPONDER

flag

為0,表示請求結束後關閉線路。然後解析第二段消息,得知其消息類型為

FCGI_PARAMS

,然後直接将消息體裡的内容以回車符切割後存入環境變量。與之類似,處理完畢之後,則傳回了

FCGI_STDOUT

消息體和

FCGI_END_REQUEST

消息體供 Web 伺服器解析。

PHP 中的 FastCGI 的實作

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

php-src/sapi/cgi/cgi_main.c

為例進行分析說明,假設開發環境為 unix 環境。main 函數中一些變量的定義,以及 sapi 的初始化,我們就不讨論在這裡讨論了,隻說明關于 FastCGI 相關的内容。

1.開啟一個 socket 監聽服務

fcgi_fd = fcgi_listen(bindpath, 128);
           

從這裡開始監聽,而

fcgi_listen

函數裡面則完成 socket 服務前三步

socket

,

bind

,

listen

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;
           

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

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;

操作,已經在前面的FastCGI 消息頭中解釋過了。

這裡是解析 FastCGI 協定的關鍵。

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

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

寫在最後

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

本文都是自己的一些了解,水準有限,如有勘誤,希望大家予以指正。

喜歡我的文章就關注我吧,持續更新中.....

以上内容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裡入手去提升,對此我整理了一些資料,包括但不限于:分布式架構、高可擴充、高性能、高并發、伺服器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點進階進階幹貨需要的可以免費分享給大家,需要的可以點選進入暗号:知乎。