PHP基礎之fastcgi協議

前言

閒來無事,決定整理一下最近看的一些東西,因而先寫寫fastcgi協議,此協議是cgi協議的升級版,其實就是當年cgi太弱,致使動態頁面太耗性能,因此開發了例如fastcgi協議等升級版,下面咱們就來聊聊這個協議的相關內容。php

CGI協議以及Fastcgi協議的介紹

CGI協議的介紹

CGI協議的誕生是爲了解決HTTP協議與編程語言之間的鏈接問題,從而減低動態頁面的開發難度。這個協議避免全部的編程語言開發動態頁面時還須要開發一套HTTP的解析庫。
那麼,關於HTTP協議自己,其實就是2個部分:請求頭部請求體。請求頭部基本上是做爲鍵值對傳輸,例如Date: Sat, 03 Feb 2018 00:14:03 GMT。請求體則是純數據流,用於傳輸文件等數據。因此,CGI自己也相應提供了2中數據格式的輸入:鍵值對輸入數據流輸入
那最初的CGI程序的輸入方式及其簡單,鍵值對數據的輸入直接利用環境變量進行傳輸,而數據流輸入則是利用標準輸入流(stdin)進行傳輸。
CGI程序的返回也包含了2種:正常數據輸出錯誤數據輸出,正常數據輸出是用於輸出處理後的數據信息,主要是HTTP的響應報文,而錯誤數據輸出則是用於在程序解析錯誤時返回給web服務器的錯誤信息,以便於web服務器作響應的處理和日誌記錄功能。正常數據輸出和錯誤數據輸出在當時的CGI程序中也理所應當的使用了標準輸出流stdout和標準錯誤流stderr。可謂是十分簡約。
下面是一個簡單的CGI程序的小栗子:html

#!/bin/sh

echo "Content-Type:text/html\n\n"
echo ""
echo ""
echo "hello! This is the PATH var:"
echo $PATH

這個CGI程序主要功能就是輸出了PATH環境變量,其中會包含請求頭部的相關信息(以後補充結果)。nginx

Fastcgi協議的介紹

可是CGI程序的弊端十分顯而易見:須要新的進程進行數據處理,數據傳輸方式沒法分佈式部署,使用進程致使容易影響系統運行,每次請求都從新加載數據耗費性能。因而乎,Fastcgi程序就是爲了解決相關問題而出現。
Fastcgi程序將CGI程序的規範都進行了保留,並將其升級,主要是將輸入和輸出的方式從標準流遷移到了socket傳輸,同時,fastcgi協議也支持將cgi程序進行守護進程化,這樣能夠提升請求的處理速度,同時提升了穩定性。c++

那麼,Fastcgi協議、php-fpmNginx三者自己是什麼關係?其實就是,Nginxweb服務器,只提供HTTP協議的輸入和輸出。php-fpmFastcgi服務器,只支持Fastcgi協議的輸入和輸出。他們2者直接由NginxHTTP協議轉換爲Fastcgi協議傳輸給php-fpm進行處理。web

Fastcgi協議的詳解

協議的組成

Fastcgi協議是由一段一段的數據段組成,能夠想象成一個車隊,每輛車裝了不一樣的數據,可是車隊的順序是固定的。輸入時順序爲:請求開始描述、請求鍵值對、請求輸入數據流。輸出時順序爲:錯誤輸出數據流、正常輸出數據流、請求結束描述。
其中鍵值對、輸入流、輸出流,錯誤流的數據和CGI程序是同樣的,只不過是換了種傳輸方式而已。
再回到車隊的描述,每輛車的結構也是統一的,在前面都有一個引擎,引擎決定了你的車是什麼樣的。因此,每一個數據塊都包含一個頭部信息,結構以下:數據庫

typedef struct {
    unsigned char version;  // 版本號
    unsigned char type;     // 記錄類型
    unsigned char requestIdB1;  // 記錄id高8位
    unsigned char requestIdB0;  // 記錄id低8位
    unsigned char contentLengthB1;  // 記錄內容長度高8位
    unsigned char contentLengthB0;  // 記錄內容長度低8位
    unsigned char paddingLength;    // 補齊位長度
    unsigned char reserved; // 真·記錄頭部補齊位
} FCGI_Header;

clipboard.png

註釋都描述的很清楚:編程

  • version爲版本號,當前只有初版本。
  • type做爲關鍵的描述,用於描述數據的類型,例如是鍵值對類型仍是數據流類型,或者是請求開始和請求結束,都是經過type進行描述。
  • requestId是記錄ID,(B1表明高位,B0表明低位,下文同理),記錄ID主要避免同一個socket通道時候傳輸的數據的正確性,同時也提升了傳輸的效率。
  • ContentLength爲數據內容的長度。
  • paddingLength是用於數據能進行8字節對齊,這樣對解析以及底層的IO操做性能有提示,因此paddingLength只是數據對8取餘,當然不會超過7
  • reserved做爲保留位,主要也是爲了協議頭部能與8字節對齊。

關於type的取值範圍:服務器

#define FCGI_BEGIN_REQUEST       1
#define FCGI_ABORT_REQUEST       2
#define FCGI_END_REQUEST         3
#define FCGI_PARAMS              4
#define FCGI_STDIN               5
#define FCGI_STDOUT              6
#define FCGI_STDERR              7
#define FCGI_DATA                8
#define FCGI_GET_VALUES          9
#define FCGI_GET_VALUES_RESULT  10
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

咱們大體按照其中的順序進行介紹。網絡

FCGI_BEGIN_REQUEST

請求輸入的時候,會帶有該類型的數據,這樣是爲了描述當前須要Fastcgi服務器充當的角色以及相關的設定。其中的數據結構爲:數據結構

typedef struct {
    unsigned char roleB1;   // 角色類型高8位
    unsigned char roleB0;   // 角色類型低8位
    unsigned char flags;    // 小紅旗
    unsigned char reserved[5];  // 補齊位
} FCGI_BeginRequestBody;

官方在升級CGI的時候,同時加入了多種角色給Fastcgi協議,其中定義爲:

#define FCGI_RESPONDER 1 
#define FCGI_AUTHORIZER 2 
#define FCGI_FILTER 3

其中FCGI_RESPONDER是咱們最多見的動態語言腳本處理角色,叫作響應器。FCGI_AUTHORIZER是用於判斷請求是否擁有訪問權限,相似於HTTP請求中的認證功能,叫作受權器。FCGI_FILTER是用於對一些特殊的數據進行處理並返回,包括添加數據頭部與尾部等功能,叫作過濾器(官方對其沒有過多的介紹,因此沒法詳細描述)。
大多數請求咱們都是使用FCGI_RESPONDER角色進行請求傳輸,由於動態語言能夠徹底的替代其餘2中角色的功能,因此受權器和過濾器的功能被你們給遺忘了。不過這不表明角色的設定是錯誤的,角色的設定很大一部分程度上給Fastcgi協議提供了快捷擴展的功能,保證了協議的可擴展性。
flags則是用於設置使用傳輸時複用通道,避免每次傳輸都須要新開一個socket通道來浪費時間和性能。

FCGI_ABORT_REQUEST

該類型主要是給web服務器提供主動結束通道的功能,場景爲當web服務器須要儘快結束並關閉通道,則會發送該請求給Fastcgi服務器,這樣Fastcgi服務會盡快的將數據處理完並返回關閉通道。

FCGI_END_REQUEST

該類型是當響應數據輸出完畢後,用於描述該請求的響應結果,相似於HTTP的響應報文的狀態碼,數據結構以下:

typedef struct {
    unsigned char appStatusB3;
    unsigned char appStatusB2;
    unsigned char appStatusB1;
    unsigned char appStatusB0;
    unsigned char protocolStatus;
    unsigned char reserved[3];
} FCGI_EndRequestBody;

其中appStatus相似於HTTP請求的狀態碼,主要用於描述數據處理的狀況,而protocolStatus主要用於對於這次請求通道的描述,是請求正常完成仍是拒絕完成等,其中的賦值範圍以下:

#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN    1
#define FCGI_OVERLOADED       2
#define FCGI_UNKNOWN_ROLE     3

區別以下:

  • FCGI_REQUEST_COMPLETE:請求的正常結束。
  • FCGI_CANT_MPX_CONN:拒絕新請求。這發生在Web服務器經過一條線路嚮應用發送併發的請求時,後者被設計爲每條線路每次處理一個請求。
  • FCGI_OVERLOADED:拒絕新請求。這發生在應用用完某些資源時,例如數據庫鏈接。
  • FCGI_UNKNOWN_ROLE:拒絕新請求。這發生在Web服務器指定了一個應用不能識別的角色時。

可是,通常狀況下,你們都只返回appStatus爲0以及protocolStatus爲0的數據。這其實也是因爲官方對相關的描述並不充分的緣由。

FCGI_PARAMS

該結果主要用於傳輸鍵值對類型數據,畢竟英文翻譯叫參數。其中該類型爲了節約空間提供了4類結構體:

typedef struct {
    unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
    unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair11;
 
typedef struct {
    unsigned char nameLengthB0;  /* nameLengthB0  >> 7 == 0 */
    unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
    unsigned char valueLengthB2;
    unsigned char valueLengthB1;
    unsigned char valueLengthB0;
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair14;

typedef struct {
    unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
    unsigned char nameLengthB2;
    unsigned char nameLengthB1;
    unsigned char nameLengthB0;
    unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair41;
 
typedef struct {
    unsigned char nameLengthB3;  /* nameLengthB3  >> 7 == 1 */
    unsigned char nameLengthB2;
    unsigned char nameLengthB1;
    unsigned char nameLengthB0;
    unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */
    unsigned char valueLengthB2;
    unsigned char valueLengthB1;
    unsigned char valueLengthB0;
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
} FCGI_NameValuePair44;

相對晦澀的話,咱們再來看看圖片描述:

clipboard.png

如圖所示,該相似分爲4個部分:nameLengthvalueLengthnameDatavalueData,其中nameLengthvalueLength用於描述長度,有1字節和4字節的2種方案,也就構成了上面的4種不一樣的結構體。其中只須要判斷第一個字節的最高位是否爲1,若爲1則是用4字節描述的長度,若爲0則用1字節。
其中,1字節是char型的大小,4字節是int型大小,因此十分方便解析。

數據流類型

類型中的FCGI_STDINFCGI_STDOUTFCGI_STDERRFCGI_DATA都是數據流傳輸,不存在什麼結構體,內容中只有數據信息。十分暴力,如圖所示:

clipboard.png

FCGI_GET_VALUES

該類型主要用於查詢fastcgi服務器的相關性能參數,結構體複用了FCGI_PARAMS類型的結構體,其中name設置爲相應的值,而value爲空便可。以後由fastcgi服務返回FCGI_GET_VALUES_RESULT類型的數據並填充value便可。其中name取值類型包括:

  • FCGI_MAX_CONNS:此應用程序將接受的最大併發傳輸鏈接數, e.g. "1" or "10".
  • FCGI_MAX_REQS:此應用程序將接受的最大併發請求數, e.g. "1" or "50".
  • FCGI_MPXS_CONNS:此應用程序將接受的最大複用傳輸鏈接數.

Fastcgi協議實例

0x0000:  0000 0000 0000 0000 0000 0000 0800 4500  ..............E.
0x0010:  03dc 535f 4000 4006 e5ba 7f00 0001 7f00  ..S_@.@.........
0x0020:  0001 ee2e 2328 3093 101a 95fd 1652 8018  ....#(0......R..
0x0030:  0156 01d1 0000 0101 080a 0004 4344 0004  .V..........CD..
0x0040:  4344 0101 0001 0008 0000 0001 0000 0000  CD..............
0x0050:  0000 0104 0001 037f 0100 0f1f 5343 5249  ............SCRI
0x0060:  5054 5f46 494c 454e 414d 452f 686f 6d65  PT_FILENAME/home
0x0070:  2f6d 6f62 792f 6e67 696e 782f 6874 6d6c  /moby/nginx/html
0x0080:  2f69 6e64 6578 2e70 6870 0c00 5155 4552  /index.php..QUER

... ...

0x03c0:  4745 7a68 2d43 4e2c 7a68 3b71 3d30 2e39  GEzh-CN,zh;q=0.9
0x03d0:  2c65 6e3b 713d 302e 3800 0104 0001 0000  ,en;q=0.8.......
0x03e0:  0000 0105 0001 0000 0000                 ..........

首先,0800以前是mac報文頭部的數據,ee2e是ip報文頭部的數據,4344以前是tcp報文的頭部,因此,0101之後,即是咱們的fastcgi的數據包信息。
0101 0001 0008 0000,咱們能夠一一對應:version:1,type:1,requestId:1,contentLength:8,padding:0,以後就是FCGI_BEGIN_REQUEST的數據包:role:1,flags:0,說明使用的是響應器功能。
以後再解析一下請求頭:version:1,type:4,requestId:1,contentLength:037f,padding:1,因此下面的結構體爲FCGI_PARAMS,繼續解析:nameLength:15,valueLength:31,nameData:SCRIPT_FILENAME,valueData:/home/moby/nginx/html/index.php,以此類推。

報文實例

POST請求報文

0x0000:  0000 0000 0000 0000 0000 0000 0800 4500  ..............E.
0x0010:  0374 0e6d 4000 4006 2b15 7f00 0001 7f00  .t.m@.@.+.......
0x0020:  0001 d5a4 2328 3da1 e47f 4aa2 48a3 8018  ....#(=...J.H...
0x0030:  0156 0169 0000 0101 080a ffff ea15 ffff  .V.i............
0x0040:  ea15 0101 0001 0008 0000 0001 0000 0000  ................
0x0050:  0000 0104 0001 02fd 0300 0f1f 5343 5249  ............SCRI
0x0060:  5054 5f46 494c 454e 414d 452f 686f 6d65  PT_FILENAME/home
0x0070:  2f6d 6f62 792f 6e67 696e 782f 6874 6d6c  /moby/nginx/html

... ...

0x0340:  5450 5f43 4f4e 4e45 4354 494f 4e6b 6565  TP_CONNECTIONkee
0x0350:  702d 616c 6976 6500 0000 0104 0001 0000  p-alive.........
0x0360:  0000 0105 0001 000c 0400 6469 6469 3d63  ..........didi=c
0x0370:  6875 7869 6e67 0000 0000 0105 0001 0000  huxing..........
0x0380:  0000                                     ..

響應報文:

0x0000:  0000 0000 0000 0000 0000 0000 0800 4500  ..............E.
0x0010:  05b4 30a7 4000 4006 069b 7f00 0001 7f00  ..0.@.@.........
0x0020:  0001 2328 ee28 d52b 8b2b 96bd f7d9 8018  ..#(.(.+.+......
0x0030:  0164 03a9 0000 0101 080a 0000 bb8d 0000  .d..............
0x0040:  bb8d 0106 0001 0564 0400 436f 6e74 656e  .......d..Conten
0x0050:  742d 7479 7065 3a20 7465 7874 2f68 746d  t-type:.text/htm
0x0060:  6c3b 2063 6861 7273 6574 3d55 5446 2d38  l;.charset=UTF-8
0x0070:  0d0a 0d0a 4172 7261 790a 280a 2020 2020  ....Array.(.....
0x0080:  5b55 5345 525d 203d 3e20 7777 772d 6461  [USER].=>.www-da
0x0090:  7461 0a20 2020 205b 484f 4d45 5d20 3d3e  ta.....[HOME].=>

... ...

0x0580:  3734 3430 322e 3335 3135 0a20 2020 205b  74402.3515.....[
0x0590:  5245 5155 4553 545f 5449 4d45 5d20 3d3e  REQUEST_TIME].=>
0x05a0:  2031 3531 3638 3734 3430 320a 290a 0000  .1516874402.)...
0x05b0:  0000 0103 0001 0008 0000 0000 0000 0000  ................
0x05c0:  0000                                     ..

等,實例能夠自行利用tcpdump工具抓取。

總結

Fastcgi協議自己完成了對CGI協議的升級,同時自身擁有一個很好的可擴展性,但自己功能的限制致使了市面上很好有協議功能的全部實現實例。但對其進行了解有利於對網絡數據的傳輸的熟悉以及加深印象。

小廣告

若對php/c++等方向感興趣且對滴滴出行有意向的小夥伴,能夠將簡歷投送一波739609084@qq.com,福利多多。

相關文章
相關標籤/搜索