閒來無事,決定整理一下最近看的一些東西,因而先寫寫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-fpm
、Nginx
三者自己是什麼關係?其實就是,Nginx
是web
服務器,只提供HTTP
協議的輸入和輸出。php-fpm
是Fastcgi
服務器,只支持Fastcgi
協議的輸入和輸出。他們2者直接由Nginx
將HTTP
協議轉換爲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;
註釋都描述的很清楚:編程
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
區別以下:
可是,通常狀況下,你們都只返回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;
相對晦澀的話,咱們再來看看圖片描述:
如圖所示,該相似分爲4個部分:nameLength
、valueLength
、nameData
和valueData
,其中nameLength
和valueLength
用於描述長度,有1
字節和4
字節的2
種方案,也就構成了上面的4
種不一樣的結構體。其中只須要判斷第一個字節的最高位是否爲1
,若爲1
則是用4
字節描述的長度,若爲0
則用1
字節。
其中,1
字節是char
型的大小,4
字節是int
型大小,因此十分方便解析。
類型中的FCGI_STDIN
、FCGI_STDOUT
、FCGI_STDERR
和FCGI_DATA
都是數據流傳輸,不存在什麼結構體,內容中只有數據信息。十分暴力,如圖所示:
FCGI_GET_VALUES
該類型主要用於查詢fastcgi
服務器的相關性能參數,結構體複用了FCGI_PARAMS
類型的結構體,其中name
設置爲相應的值,而value
爲空便可。以後由fastcgi
服務返回FCGI_GET_VALUES_RESULT
類型的數據並填充value
便可。其中name
取值類型包括:
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
,福利多多。