摘要:請求頭部基本上是作為鍵值對傳輸,例如。他們者直接由將協議轉換為協議傳輸給進行處理。而作為保留位,主要也是為了協議頭部能與字節對齊。
前言
閑來無事,決定整理一下最近看的一些東西,于是先寫寫fastcgi協議,此協議是cgi協議的升級版,其實就是當年cgi太弱,導致動態頁面太耗性能,所以開發了例如fastcgi協議等升級版,下面我們就來聊聊這個協議的相關內容。
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程序的小栗子:
#!/bin/sh echo "Content-Type:text/html " echo "" echo "" echo "hello! This is the PATH var:" echo $PATH
這個CGI程序主要功能就是輸出了PATH環境變量,其中會包含請求頭部的相關信息(之后補充結果)。
Fastcgi協議的介紹但是CGI程序的弊端十分顯而易見:需要新的進程進行數據處理,數據傳輸方式無法分布式部署,使用進程導致容易影響系統運行,每次請求都重新加載數據耗費性能。于是乎,Fastcgi程序就是為了解決相關問題而出現。
Fastcgi程序將CGI程序的規范都進行了保留,并將其升級,主要是將輸入和輸出的方式從標準流遷移到了socket傳輸,同時,fastcgi協議也支持將cgi程序進行守護進程化,這樣可以提高請求的處理速度,同時提高了穩定性。
那么,Fastcgi協議、php-fpm、Nginx三者本身是什么關系?其實就是,Nginx是web服務器,只提供HTTP協議的輸入和輸出。php-fpm是Fastcgi服務器,只支持Fastcgi協議的輸入和輸出。他們2者直接由Nginx將HTTP協議轉換為Fastcgi協議傳輸給php-fpm進行處理。
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通道來浪費時間和性能。
該類型主要是給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;
相對晦澀的話,我們再來看看圖片描述:
如圖所示,該類似分為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取值類型包括:
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,福利多多。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28214.html
摘要:通過,腳本層無需過多考慮執行的具體環境,而本身則可以讓針對自己的特點給出特有實現。模式下,也只執行一次。這幾個概念的關系如下網關協議,與語言無關,所以與關系也不大。總結本文簡要回顧了程序的架構和執行流程,并對幾個容易混淆概念做了介紹。 轉載請注明文章出處:https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎 web請求 cookie we...
摘要:請求頭部基本上是作為鍵值對傳輸,例如。他們者直接由將協議轉換為協議傳輸給進行處理。而作為保留位,主要也是為了協議頭部能與字節對齊。 前言 閑來無事,決定整理一下最近看的一些東西,于是先寫寫fastcgi協議,此協議是cgi協議的升級版,其實就是當年cgi太弱,導致動態頁面太耗性能,所以開發了例如fastcgi協議等升級版,下面我們就來聊聊這個協議的相關內容。 CGI協議以及Fastc...
摘要:與傳統模式的區別之一則是服務器不是直接執行程序了,而是通過與響應器進程管理器進行交互,服務器需要將接口數據封裝在遵循協議包中發送給響應器程序。正是由于進程管理器是基于通信的,所以也是分布式的,服務器和響應器服務器分開部署。 廣告 很多工程師在工作1~3年的時候最容易遇到瓶頸,不知道自己應該學習什么,面試總是吃閉門羹。那么 PHP 后面應該怎么學呢?安利一波我的系列直播 《PHP 進階之...
摘要:業務和架構不分家,架構是建立在對業務的理解之上的。主鍵最好保持順序遞增,隨機主鍵會導致聚簇索引樹頻繁分裂,隨機增多,數據離散,性能下降。沒有索引的更新,可能會導致全表數據都被鎖住。 本博客并非全部原創,其實是一個知識的歸納和匯總,里面我引用了很多網上、書上的內容。也給出了相關的鏈接。 本文涉及的知識點比較多,大家可以根據關鍵字去搜索相關的內容和購買相應的書籍進行系統的學習。不對的地方...
摘要:要說與是如何協同工作的,首先得說和這兩個協議。之于標準的,也提供了一些增強功能,具體可以參考官方文檔。為了能夠使理解協議,提供了模塊來將請求映射為對應的請求。 網絡上有很多關于如何配置 Nginx + FPM 的文章,但它們更多從操作的角度出發,告訴我們怎么做,但卻沒有告訴我們為什么要這么做,本文從 Nginx 與 FPM 的工作機制出發,探討配置背后的原理,讓我們真正理解 Nginx...
閱讀 2902·2023-04-26 02:14
閱讀 3751·2019-08-30 15:55
閱讀 1843·2019-08-29 16:42
閱讀 2757·2019-08-26 11:55
閱讀 2846·2019-08-23 13:38
閱讀 480·2019-08-23 12:10
閱讀 1308·2019-08-23 11:44
閱讀 2790·2019-08-23 11:43