【PHP7源碼學習】2019-04-10 FastCGI協議2

baiyanphp

所有視頻:https://segmentfault.com/a/11...html

引入

  • 那麼,咱們今天詳細解釋一下圖中的FastCGI協議的部分。其實,最開始咱們是使用CGI協議的,可是CGI程序的弊端十分明顯,如須要新的進程進行數據處理,效率低下。FastCGI協議就是爲了解決CGI協議的相關問題而出現,是CGI協議的升級版。
  • 咱們學習一個協議,最重要的就是它的格式與語法,看它如何組織所要傳輸數據的格式,讓接收方可以更加方便地接收。那麼,這個協議須要解決以下幾個問題:
  • 標識一個請求的開始與結束,讓數據包在繁雜的TCP數據流中擁有清晰的邊界,方便讀取
  • 傳輸其餘附加參數(如定義在nginx中的fastcgi_param各項參數)
  • 傳輸一個客戶端發來請求的原始數據
  • 針對上面一條提到在nginx配置文件中的其餘附加參數,有以下一些形式,你們應該比較熟悉了:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;#腳本文件請求的路徑,也就是說當訪問127.0.0.1/index.php的時候,須要讀取網站根目錄下面的index.php文件,若是沒有配置這一配置項時,nginx不回去網站根目錄下訪問.php文件,因此返回空白
fastcgi_param QUERY_STRING $query_string;                        #請求的參數;如?app=123
fastcgi_param REQUEST_METHOD $request_method;                    #請求的動做(GET,POST)
fastcgi_param CONTENT_TYPE $content_type;                        #請求頭中的Content-Type字段
fastcgi_param CONTENT_LENGTH $content_length;                    #請求頭中的Content-length字段。

fastcgi_param SCRIPT_NAME $fastcgi_script_name;                  #腳本名稱 
fastcgi_param REQUEST_URI $request_uri;                          #請求的地址不帶參數
fastcgi_param DOCUMENT_URI $document_uri;                        #與$uri相同。 
fastcgi_param DOCUMENT_ROOT $document_root;                      #網站的根目錄。在server配置中root指令中指定的值 
fastcgi_param SERVER_PROTOCOL $server_protocol;                  #請求使用的協議,一般是HTTP/1.0或HTTP/1.1。

fastcgi_param GATEWAY_INTERFACE CGI/1.1;                         #cgi 版本
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;              #nginx 版本號,可修改、隱藏

fastcgi_param REMOTE_ADDR $remote_addr;                          #客戶端IP
fastcgi_param REMOTE_PORT $remote_port;                          #客戶端端口
fastcgi_param SERVER_ADDR $server_addr;                          #服務器IP地址
fastcgi_param SERVER_PORT $server_port;                          #服務器端口
fastcgi_param SERVER_NAME $server_name;                          #服務器名,域名在server配置中指定的server_name

fastcgi_param PATH_INFO $path_info;                             #可自定義變量

-- PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
  • 那麼,咱們在PHP中便可打印出上面的服務環境變量。如:
echo $_SERVER['REMOTE_ADDR']
  • 帶着以上幾個問題,咱們來由外到內一步步剖析,爲何FastCGI協議是這樣設計的。

FastCGI的設計思想與結構

  • 首先咱們基於以前的客戶端、nginx、PHP-FPM之間通訊流程圖,放大nginx與PHP-FPM之間通訊的數據流:

  • 爲了解決咱們以前談到的三個問題,FastCGI把包分爲多種類型,每種類型作它本身的事情。如圖中的FCGI_BEGIN_REQUEST類型,負責標識請求的開始,FCGI_PARAMS類型負責發送nginx中配置的參數,FCGI_STDIN類型存儲客戶端發送的原始字節流數據。這樣一次請求的全部數據纔可以成功送達到PHP-FPM。咱們看一下FastCGI數據包的全部類型:
#define FCGI_BEGIN_REQUEST       1                     //(web->fastcgi)請求開始數據包
#define FCGI_ABORT_REQUEST       2                     //(web->fastcgi)終止請求
#define FCGI_END_REQUEST         3                     //(fastcgi->web)請求結束
#define FCGI_PARAMS              4                     //(web->fastcgi)傳遞參數
#define FCGI_STDIN               5                     //(web->fastcgi)數據流傳輸數據
#define FCGI_STDOUT              6                     //(fastcgi->web)數據流傳輸數據
#define FCGI_STDERR              7                     //(fastcgi->web)數據流傳輸
#define FCGI_DATA                8                     //(web->fastcgi)數據流傳輸
#define FCGI_GET_VALUES          9                     //(web->fastcgi)查詢fastcgi服務器性能參數
#define FCGI_GET_VALUES_RESULT  10                     //(fastcgi->web)fastcgi性能參數查詢返回
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
  • 咱們從宏觀層面看完了FastCGI包,咱們深刻每一個包的內部結構。經過上一篇筆記的學習咱們知道,TCP/IP等協議的數據包,一般都是數據包頭部+包體的結構,頭部字段一般是一些描述信息,包體才真正地存儲數據,這裏FastCGI協議也不例外:

  • 在代碼層面,它的結構以下:
typedef struct _fcgi_begin_request_rec {
    fcgi_header hdr; //包頭部
    fcgi_begin_request body; //包體
} fcgi_begin_request_rec;

FastCGI數據包頭部

  • FastCGI數據包頭部結構定義以下:
typedef struct {
    unsigned char version;            // 協議版本號
    unsigned char type;               // 數據包類型
    unsigned char requestIdB1;        // 包惟一標識id的高8位
    unsigned char requestIdB0;        // 包惟一標識id的低8位
    unsigned char contentLengthB1;    // 記錄內容長度高8位(body長度高8位)
    unsigned char contentLengthB0;    // 記錄內容長度低8位(body長度低8位)
    unsigned char paddingLength;      // 補齊位長度(body補齊長度)
    unsigned char reserved;           // 字節補齊位
}Header;
  • 一般狀況下,每個FastCGI數據包都有一個頭部,大小爲8個字節,用來記錄當前數據包的一些輔助信息,如數據包類型(須要確認當前包屬於剛纔咱們列舉的哪一種類型)、惟一標識包的id、還有包體的長度、以及字節對齊(確保是2的整數次冪)
  • 雖然在一般狀況下,每一種FastCGI類型的數據包都有相同結構的包頭,可是它們之間包體部分的結構就不太同樣了。
  • 下面咱們以一個請求從nginx到PHP-FPM的數據包流動方向(FCGI_BEGIN_REQUEST->FCGI_PARAMS->FCGI_STDIN)爲例,講解一下FastCGI協議的類型。

FCGI_BEGIN_REQUEST類型

  • FCGI_BEGIN_REQUEST類型的數據包表明一個請求數據包的開始
  • 因爲數據包頭部的結構已經介紹完畢了,接下來咱們看一下FCGI_BEGIN_REQUEST類型包體部分的結構,它是一個結構體:
typedef struct _fcgi_begin_request {
    unsigned char roleB1; //
    unsigned char roleB0;
    unsigned char flags;
    unsigned char reserved[5];
} fcgi_begin_request;
  • FCGI_BEGIN_REQUEST的包體大小爲8個字節,其中role字段是爲了描述當前須要FastCGI服務器(即 PHP-FPM)充當的角色,有FCGI_RESPONDER,FCGI_AUTHORIZER 和FCGI_FILTER。
FCGI_RESPONDER:最多見的動態語言腳本處理角色,叫作響應器。
FCGI_AUTHORIZER:用於判斷請求是否擁有訪問權限,相似於HTTP請求中的認證功能,叫作受權器。
FCGI_FILTER:用於對一些特殊的數據進行處理並返回,包括添加數據頭部與尾部等功能,叫作過濾器(官方對其沒有過多的介紹,因此沒法詳細描述)。
大多數請求咱們都是使用FCGI_RESPONDER角色進行請求傳輸,由於動態語言能夠徹底的替代其餘2中角色的功能,因此受權器和過濾器的功能被你們給遺忘了。不過這不表明角色的設定是錯誤的,角色的設定很大一部分程度上給Fastcgi協議提供了快捷擴展的功能,保證了協議的可擴展性。
flags則是用於設置使用傳輸時複用通道,避免每次傳輸都須要新開一個socket通道來浪費時間和性能。

FCGI_PARAMS類型

  • 在nginx配置文件中,配置的FastCGI的參數均以參數名-值的形式出現,那麼能夠用一種key-value對的結構來對其進行存儲,而它確實也是這樣設計的:

  • 咱們能夠大致上看出,FCGI_PARAMS的包體以key-value對形式出現。整個數據包的存儲數據爲包頭部、key的長度、value的長度、key數據、value數據的順序出現。
  • 這裏它用了一個技巧。爲了節省空間,當key或者value的數據長度小於等於127字節的時候,key和value的長度兩個字段採用1個字節來表示;當大於128字節的時候,採用4個字節來表示。那麼爲何選127做爲分界線呢?由於127的二進制位01111111,從128開始,最高爲爲1,因此只須要判斷最高位是否爲1,就能夠知道key或者value長度的字段佔用1個字節仍是4個字節。若是最高位爲1,則佔用4個字節;若是最高位爲0,則佔用1個字節。

FCGI_STDIN類型

  • FCGI_STDIN存儲從客戶端發出的原始數據,注意這裏的數據是以字節流存儲的,而並非存在一個固定的結構體中:

  • 至此,一次nginx到PHP-FPM的請求就完成了。

抓包示例

13:50:43.883594 IP VM_0_3_centos.33844 > VM_0_3_centos.cslistener: Flags [P.], seq 608546014:608546982, ack 2973795482, win 342, options [nop,nop,TS val 961901286 ecr 961901286], length 968
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500  ..............E.
    0x0010:  03fc de3d 4000 4006 5abc 7f00 0001 7f00  ...=@.@.Z.......
    0x0020:  0001 8434 2328 2445 acde b140 849a 8018  ...4#($E...@....
    0x0030:  0156 01f1 0000 0101 080a 3955 72e6 3955  .V........9Ur.9U
    0x0040:  72e6 0101 0001 0008 0000 0001 0000 0000  r...............
    0x0050:  0000 0104 0001 03a0 0000 0f35 5343 5249  ...........5SCRI
    0x0060:  5054 5f46 494c 454e 414d 452f 6461 7461  PT_FILENAME/data
    0x0070:  2f77 7777 2f68 7464 6f63 732f 6461 7461  /www/htdocs/data
    0x0080:  2f77 7777 2f68 7464 6f63 732f 736e 6f2f  /www/htdocs/sno/
    0x0090:  7075 626c 6963 2f69 6e64 6578 2e70 6870  public/index.php
    0x00a0:  0c00 5155 4552 595f 5354 5249 4e47 0e03  ..QUERY_STRING..
    0x00b0:  5245 5155 4553 545f 4d45 5448 4f44 4745  REQUEST_METHODGE
    0x00c0:  540c 0043 4f4e 5445 4e54 5f54 5950 450e  T..CONTENT_TYPE.
    0x00d0:  0043 4f4e 5445 4e54 5f4c 454e 4754 480b  .CONTENT_LENGTH.
    0x00e0:  0a53 4352 4950 545f 4e41 4d45 2f69 6e64  .SCRIPT_NAME/ind
    0x00f0:  6578 2e70 6870 0b01 5245 5155 4553 545f  ex.php..REQUEST_
    0x0100:  5552 492f 0c01 444f 4355 4d45 4e54 5f55  URI/..DOCUMENT_U
    0x0110:  5249 2f0d 2b44 4f43 554d 454e 545f 524f  RI/.+DOCUMENT_RO
    0x0120:  4f54 2f64 6174 612f 7777 772f 6874 646f  OT/data/www/htdo
    0x0130:  6373 2f64 6174 612f 7777 772f 6874 646f  cs/data/www/htdo
    0x0140:  6373 2f73 6e6f 2f70 7562 6c69 630f 0853  cs/sno/public..S
    0x0150:  4552 5645 525f 5052 4f54 4f43 4f4c 4854  ERVER_PROTOCOLHT
    0x0160:  5450 2f31 2e31 0e04 5245 5155 4553 545f  TP/1.1..REQUEST_
    0x0170:  5343 4845 4d45 6874 7470 1107 4741 5445  SCHEMEhttp..GATE
    0x0180:  5741 595f 494e 5445 5246 4143 4543 4749  WAY_INTERFACECGI
    0x0190:  2f31 2e31 0f0c 5345 5256 4552 5f53 4f46  /1.1..SERVER_SOF
    0x01a0:  5457 4152 456e 6769 6e78 2f31 2e31 312e  TWAREnginx/1.11.
    0x01b0:  390b 0f52 454d 4f54 455f 4144 4452 3131  9..REMOTE_ADDR11
    0x01c0:  332e 3232 372e 3234 392e 3132 370b 0552  3.227.249.127..R
    0x01d0:  454d 4f54 455f 504f 5254 3533 3931 330b  EMOTE_PORT53913.
    0x01e0:  0a53 4552 5645 525f 4144 4452 3137 322e  .SERVER_ADDR172.
    0x01f0:  3136 2e30 2e33 0b02 5345 5256 4552 5f50  16.0.3..SERVER_P
    0x0200:  4f52 5438 300b 0d53 4552 5645 525f 4e41  ORT80..SERVER_NA
    0x0210:  4d45 6772 6170 652e 7961 662e 636f 6d0f  MEgrape.yaf.com.
    0x0220:  0352 4544 4952 4543 545f 5354 4154 5553  .REDIRECT_STATUS
    0x0230:  3230 3009 0f48 5454 505f 484f 5354 3132  200..HTTP_HOST12
    0x0240:  322e 3135 322e 3232 392e 3232 310f 0a48  2.152.229.221..H
    0x0250:  5454 505f 434f 4e4e 4543 5449 4f4e 6b65  TTP_CONNECTIONke
    0x0260:  6570 2d61 6c69 7665 1209 4854 5450 5f43  ep-alive..HTTP_C
    0x0270:  4143 4845 5f43 4f4e 5452 4f4c 6d61 782d  ACHE_CONTROLmax-
    0x0280:  6167 653d 301e 0148 5454 505f 5550 4752  age=0..HTTP_UPGR
    0x0290:  4144 455f 494e 5345 4355 5245 5f52 4551  ADE_INSECURE_REQ
    0x02a0:  5545 5354 5331 0f79 4854 5450 5f55 5345  UESTS1.yHTTP_USE
    0x02b0:  525f 4147 454e 544d 6f7a 696c 6c61 2f35  R_AGENTMozilla/5
    0x02c0:  2e30 2028 4d61 6369 6e74 6f73 683b 2049  .0.(Macintosh;.I
    0x02d0:  6e74 656c 204d 6163 204f 5320 5820 3130  ntel.Mac.OS.X.10
    0x02e0:  5f31 355f 3029 2041 7070 6c65 5765 624b  _15_0).AppleWebK
    0x02f0:  6974 2f35 3337 2e33 3620 284b 4854 4d4c  it/537.36.(KHTML
    0x0300:  2c20 6c69 6b65 2047 6563 6b6f 2920 4368  ,.like.Gecko).Ch
    0x0310:  726f 6d65 2f37 352e 302e 3337 3730 2e31  rome/75.0.3770.1
    0x0320:  3030 2053 6166 6172 692f 3533 372e 3336  00.Safari/537.36
    0x0330:  0b76 4854 5450 5f41 4343 4550 5474 6578  .vHTTP_ACCEPTtex
    0x0340:  742f 6874 6d6c 2c61 7070 6c69 6361 7469  t/html,applicati
    0x0350:  6f6e 2f78 6874 6d6c 2b78 6d6c 2c61 7070  on/xhtml+xml,app
    0x0360:  6c69 6361 7469 6f6e 2f78 6d6c 3b71 3d30  lication/xml;q=0
    0x0370:  2e39 2c69 6d61 6765 2f77 6562 702c 696d  .9,image/webp,im
    0x0380:  6167 652f 6170 6e67 2c2a 2f2a 3b71 3d30  age/apng,*/*;q=0
    0x0390:  2e38 2c61 7070 6c69 6361 7469 6f6e 2f73  .8,application/s
    0x03a0:  6967 6e65 642d 6578 6368 616e 6765 3b76  igned-exchange;v
    0x03b0:  3d62 3314 0d48 5454 505f 4143 4345 5054  =b3..HTTP_ACCEPT
    0x03c0:  5f45 4e43 4f44 494e 4767 7a69 702c 2064  _ENCODINGgzip,.d
    0x03d0:  6566 6c61 7465 140e 4854 5450 5f41 4343  eflate..HTTP_ACC
    0x03e0:  4550 545f 4c41 4e47 5541 4745 7a68 2d43  EPT_LANGUAGEzh-C
    0x03f0:  4e2c 7a68 3b71 3d30 2e39 0104 0001 0000  N,zh;q=0.9......
    0x0400:  0000 0105 0001 0000 0000                 ..........
  • 根據上一篇筆記咱們學到的數據包結構,咱們可以將數據包分解爲如下結構(加粗的數字爲首部長度,乘以4就是總字節數):
  • MAC幀頭部(14字節):0000 0000 0000 0000 0000 0000 0800
  • IP頭部(20字節):4500 03fc de3d 4000 4006 5abc 7f00 0001 7f00
  • TCP頭部(32字節):8434(33844端口) 2328(9000端口) 2445 acde b140 849a 8018 0156 01f1 0000 0101 080a 3955 72e6 3955 72e6
  • 接下來就是FastCGI協議數據包的部分了,首先應該是一個FCGI_BEGIN_REQUEST類型的數據包:
  • 包頭:0101 0001 0008 0000nginx

    • version:01(FastCGI協議版本爲1)
    • type:01(對應FCGI_BEGIN_REQUEST)
    • requestIdB1:00
    • requestIdB0:01(表明是1號數據包)
    • contentLengthB1:00
    • contentLengthB0:08(表明包體佔用8個字節)
    • paddingLength:00(補齊位長度爲0)
    • reserved:00(對齊位無效)
  • 包體:0001 0000 0000 0000web

    • roleB1:1(表明充當的是響應器角色)
    • roleB0:0
    • flags:0
    • reserved[5]:0(對齊位無效)
  • 那麼接下來應該是一個FCGI_PARAMS類型的數據包了:
  • 包頭:0104 0001 03a0 0000segmentfault

    • version:01
    • type:04(對應FCGI_PARAMS)
    • requestIdB1:00
    • requestIdB0:01(表明是1號數據包)
    • contentLengthB1:03
    • contentLengthB0:a0(表明包體佔用928個字節)
    • paddingLength:00(補齊位長度爲0)
    • reserved:00(對齊位無效)
    • 因爲這個包體是很是長的,咱們選擇其中一個key-value對:
  • 包體:0f 35centos

    • 緊挨着包頭的應該是存儲key長度的字段,既然它最高位爲0(0=0000),那麼key的長度只需用1個字節存儲,長度爲15字節(0f)。而後緊挨着的應該是存儲value長度的字段,它的最高位也爲0(3=0011),故value的長度也須要1個字節存儲,長度爲53字節。
    • 而後緊挨着的應該是key的內容:5343 5249 5054 5f46 494c 454e 414d 45,一共15字節,根據ASCII碼翻譯以後,其值爲SCRIPT_FILENAME。再往下數53個字節,應該就是value的內容:2f 6461 7461 2f77 7777 2f68 7464 6f63 732f 6461 7461 2f77 7777 2f68 7464 6f63 732f 736e 6f2f 7075 626c 6963 2f69 6e64 6578 2e70 6870,其翻譯後的值爲/data/www/htdocs/data/www/htdocs/sno/public/index.php。
  • 咱們往下繼續數,直至第928個字節,還有其餘的各項參數,咱們在此再也不一一列舉。而後就是FCGI_STDIN類型的數據包,存儲着咱們客戶端的原始數據。咱們再此就再也不贅述,有興趣的同窗能夠繼續跟進一下。
相關文章
相關標籤/搜索