摘要:全部視頻引入讀這篇文章之前請先閱讀源碼學習協議我們知道,客戶端之間通信的方式如下那么,我們今天詳細解釋一下圖中的協議的部分。協議就是為了解決協議的相關問題而出現,是協議的升級版。在配置中指令中指定的值請求使用的協議,通常是或。
baiyan
全部視頻:https://segmentfault.com/a/11...
引入讀這篇文章之前請先閱讀【PHP源碼學習】2019-04-09 FastCGI協議1
我們知道,客戶端、nginx、PHP-FPM之間通信的方式如下:
那么,我們今天詳細解釋一下圖中的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_PARAMS類型
FCGI_AUTHORIZER:用于判斷請求是否擁有訪問權限,類似于HTTP請求中的認證功能,叫做授權器。
FCGI_FILTER:用于對一些特殊的數據進行處理并返回,包括添加數據頭部與尾部等功能,叫做過濾器(官方對其沒有過多的介紹,所以無法詳細描述)。
大多數請求我們都是使用FCGI_RESPONDER角色進行請求傳輸,因為動態語言可以完全的替代其他2中角色的功能,所以授權器和過濾器的功能被大家給遺忘了。不過這不代表角色的設定是錯誤的,角色的設定很大一部分程度上給Fastcgi協議提供了快捷擴展的功能,保證了協議的可擴展性。
flags則是用于設置使用傳輸時復用通道,避免每次傳輸都需要新開一個socket通道來浪費時間和性能。
在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 0000
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 0000
roleB1:1(代表充當的是響應器角色)
roleB0:0
flags:0
reserved[5]:0(對齊位無效)
那么接下來應該是一個FCGI_PARAMS類型的數據包了:
包頭:0104 0001 03a0 0000
version:01
type:04(對應FCGI_PARAMS)
requestIdB1:00
requestIdB0:01(代表是1號數據包)
contentLengthB1:03
contentLengthB0:a0(代表包體占用928個字節)
paddingLength:00(補齊位長度為0)
reserved:00(對齊位無效)
由于這個包體是非常長的,我們選擇其中一個key-value對:
包體:0f 35
緊挨著包頭的應該是存儲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類型的數據包,存儲著我們客戶端的原始數據。我們再此就不再贅述,有興趣的同學可以繼續跟進一下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40540.html
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
摘要:所以,它就會將端口號還有一些額外的信息被稱作首部,和應用層下發的數據部分進行封裝,一起傳給下一層即網絡層。它們之間的通信,屬于同一機器上不同端口號之間的通信。而協議傳輸的僅僅是無意義的字節流數據,接收方并不能正確讀取數據的含義。 baiyan 全部視頻:https://segmentfault.com/a/11... 計算機網絡架構的分層與封裝 我們經常談到,計算機網絡有多種體系架構...
摘要:,配是通過一個類似的協議,升級版的的。在上有幫你管理進程,在似乎沒有,這是有點令人悲傷的。檢驗一下然后開啟然后配置中里文件在盤建立一個的文件夾,放入,開啟測試寫入訪問應用我的項目就用了這個東西,,歡迎 fastcgi As we all know,nginx配php是通過fastcgi(一個類似http的協議,升級版的cgi)的。在linux上有php-fpm幫你管理進程,在windo...
閱讀 2290·2023-04-26 00:01
閱讀 796·2021-10-27 14:13
閱讀 1810·2021-09-02 15:11
閱讀 3381·2019-08-29 12:52
閱讀 528·2019-08-26 12:00
閱讀 2569·2019-08-26 10:57
閱讀 3405·2019-08-26 10:32
閱讀 2848·2019-08-23 18:29