基於:php
csdn1html
婁神的描述git
其實看上面兩位大佬的博客就已經ojbk
了.寫的目地主要是本身總結學習一下.github
1.基礎的 WebServer
應該支持客戶端請求靜態文件和動態文件.
2. 瀏覽器是不可以解析動態的php
文件的!那麼咱們編寫服務器程序時候若是遇到請求.php
動態文件時就應該將php
文件翻譯爲html
文件.
3. php-fpm
就可以將php
文件翻譯爲html
文件.因此咱們的webserver
將經過進程間通訊把php
文件交給php-fpm
,而後把php-fpm
翻譯事後的html
文件發給客戶端便可,(php-fpm)就等價於一個CGI 服務器
.
4.那麼咱們如何才能讓php-fpm
幫咱們解析咱們想要翻譯成.html文件的.php文件呢?經過**fastcgi
協議,其實就是WebServer
與php-fpm
之間通訊的規則(或者說是'語言')**web
和’任何協議
同樣,fastcgi
協議也有一個消息頭或者叫作請求頭.其格式是固定的.用以表示消息體的類型和信息.任意一個FastCGI數據包必須以一個8字節的消息頭開始編程
typedef struct { unsigned char version; //FCGI版本信息,目前通常定義爲1 unsigned char type; //每次發送的消息的類型.至關於flag,具體表示見下面: unsigned char requestIdB1; //合起來表示本次請求的編號 ID unsigned char requestIdB0; unsigned char contentLengthB1; //合起來表示 body 長度 unsigned char contentLengthB0; unsigned char paddingLength; //填充字節長度,填充長度不可超過255字節 unsigned char reserved; //保留字節 } FCGI_Header; //消息頭
type
字段分別是以下含義:瀏覽器
// FCGI_Header 中 type 的具體值 #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 /*傳遞參數,代表消息中包含的數據爲某個name-value對 (web->fastcgi)*/ #define FCGI_STDIN 5 /*POST 內容傳遞,從瀏覽器接收到的POST請求數據(表單提交等)以消息的形式發給php-fpm時, 這種消息的type就得設爲5(web->fastcgi)*/ #define FCGI_STDOUT 6 //正常響應內容,php-fpm給web服務器回的正常響應消息的type就設爲6(fastcgi->web) #define FCGI_STDERR 7 //php-fpm給web服務器回的錯誤響應設爲7(fastcgi->web) #define FCGI_DATA 8 //向CGI程序傳遞的額外數據(WEB->FastCGI) #define FCGI_GET_VALUES 9 // 向FastCGI程序詢問一些環境變量(WEB->FastCGI) #define FCGI_GET_VALUES_RESULT 10 // 詢問環境變量的結果(FastCGI->WEB) #define FCGI_UNKNOWN_TYPE 11 //通知 webserver 所請求 type 非正常類型 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) // 未知類型,可能用做拓展
requestIdB1
,requestIdB0
合起來表示本次請求的編號,其中requestIdB1
是請求編號的高八位,requestIdB0
是請求編號的低八位。這個字段的存在容許Web服務器在一次鏈接中向FastCGI服務器發送多個不一樣的請求,只要使用不一樣的請求編號便可服務器
contentLengthB1`` contentLengthB0
)合起來表示消息頭後仍有多少字節的數據(數據長度),contentLengthB1
表示其高八位,contentLengthB0
表示其低八位。數據長度的表示範圍在0~65535(即2^16-1)之間,於是若數據超過65535字節,則必須將之分爲多個數據包來傳輸(編程中要注意這一點)網絡
//FCGI的版本 #define FCGI_VERSION_1 1 FCGI_Header makeHeader(int type, int requestId, int contentLength, int paddingLength) { FCGI_Header header; header.version = FCGI_VERSION_1; header.type = (unsigned char)type; /* 兩個字段保存請求ID */ header.requestIdB1 = (unsigned char)((requestId >> 8) & 0xff); header.requestIdB0 = (unsigned char)(requestId & 0xff); /* 兩個字段保存內容長度 */ header.contentLengthB1 = (unsigned char)((contentLength >> 8) & 0xff); header.contentLengthB0 = (unsigned char)(contentLength & 0xff); /* 填充字節的長度 */ header.paddingLength = (unsigned char)paddingLength; /* 保存字節賦爲 0 */ header.reserved = 0; return header; }
相似於http
協議,在咱們發送完消息頭以後,咱們就須要發送消息體了.那這裏確定仍是會由於type
的不一樣而有所不一樣.app
這種消息是一中固定的8
字節結構,所以咱們會推出消息頭中的contentLengthBx
在這種狀況下確定也是固定的.
typedef struct { unsigned char roleB1; unsigned char roleB0; //合起來表示 webserver 所指望php-fpm 扮演的角色,具體取值下面有 unsigned char flags; //肯定 php-fpm 處理完一次請求以後是否關閉,flag=1,不關閉 unsigned char reserved[5]; //保留字段 } FCGI_BeginRequestBody; //開始請求體 //webserver 指望 php-fpm 扮演的角色(想讓php-fpm作什麼) #define FCGI_RESPONDER 1 //接受http關聯的全部信息,併產生http響應,接受來自webserver的PARAMS環境變量 #define FCGI_AUTHORIZER 2 //對於認證的會關聯其http請求,未認證的則關閉請求 #define FCGI_FILTER 3 //過濾web server 中的額外數據流,併產生過濾後的http響應
總的來說,fastcgi
協議中規定了三種角色,有:
enum FCGI_Role { FCGI_RESPONDER = 1, // 響應器,php-fpm接受咱們的http所關聯的信息,併產生響應 FCGI_AUTHORIZER = 2, //認證器,php-fpm會對咱們的請求進行認證,認證經過的其會返回響應,認證不經過則關閉請求 FCGI_FILTER = 3 // 過濾器,過濾請求中的額外數據流,併產生過濾後的http響應 };
通常,咱們的webserver
就把它看成響應器就好了(也就是說把該字段設爲FCGI_RESPONDER
)
typedef struct { int sockfd_; //與php-fpm 創建的 sockfd int requestId_; //record 裏的請求ID int flag_; //用來標誌當前讀取內容是否爲html內容 } FastCgi_t; void FastCgi_init(FastCgi_t *c) { c->sockfd_ = 0; //與php-fpm 創建的 sockfd c->flag_ = 0; //record 裏的請求ID c->requestId_ = 0; //用來標誌當前讀取內容是否爲html內容 } void setRequestId(FastCgi_t *c, int requestId) { c->requestId_ = requestId; } int main() { FastCgi_t *c; c = (FastCgi_t *)malloc(sizeof(FastCgi_t)); FastCgi_init(c); setRequestId(c, 1); /*1 用來代表此消息爲請求開始的第一個消息*/ startConnect(c); //略,就是與127.0.0.1 9000 創建了一個鏈接 sendStartRequestRecord(c); //主要是這個函數!!!! sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php"); sendParams(c, "REQUEST_METHOD", "POST"); ... }
sendStartRequestRecord(c)
第一次請求函數:
typedef struct { unsigned char roleB1; unsigned char roleB0; unsigned char flags; //肯定 php-fpm 處理完一次請求以後是否關閉 unsigned char reserved[5]; //保留字段 } FCGI_BeginRequestBody; //開始請求體 typedef struct { FCGI_Header header; //消息頭 FCGI_BeginRequestBody body; //開始請求體 } FCGI_BeginRequestRecord; //完整消息--開始 FCGI_BeginRequestBody makeBeginRequestBody(int role, int keepConnection) { FCGI_BeginRequestBody body; /* 兩個字節保存指望 php-fpm 扮演的角色 */ body.roleB1 = (unsigned char)((role >> 8) & 0xff); body.roleB0 = (unsigned char)(role & 0xff); /* 大於0長鏈接,不然短鏈接 */ body.flags = (unsigned char)((keepConnection) ? FCGI_KEEP_CONN : 0); bzero(&body.reserved, sizeof(body.reserved)); return body; } int sendStartRequestRecord(FastCgi_t *c) { int rc; FCGI_BeginRequestRecord beginRecord; beginRecord.header = makeHeader(FCGI_BEGIN_REQUEST, c->requestId_, sizeof(beginRecord.body), 0); beginRecord.body = makeBeginRequestBody(FCGI_RESPONDER, 0); rc = write(c->sockfd_, (char *)&beginRecord, sizeof(beginRecord)); assert(rc == sizeof(beginRecord)); return 1; }
8字節固定格式:
typedef struct { unsigned char appStatusB3; unsigned char appStatusB2; unsigned char appStatusB1; unsigned char appStatusB0; //合起來表示CGI程序的結束狀態,0爲正常,此處是一個網絡字節序,須要手動轉換 unsigned char protocolStatus; //fastcgi協議狀態,以下: unsigned char reserved[3]; } FCGI_EndRequestBody; //結束消息體
//幾種結束狀態 #define FCGI_REQUEST_COMPLETE 0 //正常結束 #define FCGI_CANT_MPX_XONN 1 //拒絕新請求,單線程 #define FCGI_OVERLOADED 2 //拒絕新請求,應用負載了 #define FCGI_UNKNOWN_ROLE 3 //webserver 指定了一個應用不能識別的角色
向php-fpm
傳遞name-value對,可傳遞本身的,也能夠傳遞fastcgi
提供的.fasttcgi
提供的name
主要有以下這些:
name名 | 含義 |
---|---|
*SCRIPT_NAME | 要執行的CGI程序的名字 |
*REQUEST_METHOD | 信息傳輸方式(GET/POST/PUT等) |
*QUERY_STRING | 查詢字符串 |
CONTENT_LENGTH | 向CGI標準輸入傳遞的信息長度(應當等於FCGI_STDIN消息contentLength字段之和) |
CONTENT_TYPE | 向CGI標準輸入傳遞的信息類型 |
其他更多的可參考婁神的boke
#include <stdio.h> #include <stdlib.h> #include "fcgi.h" #include <sys/types.h> #include <sys/socket.h> int main() { FastCgi_t *c; c = (FastCgi_t *)malloc(sizeof(FastCgi_t)); FastCgi_init(c); setRequestId(c, 1); /*1 用來代表此消息爲請求開始的第一個消息*/ startConnect(c); sendStartRequestRecord(c); sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php"); sendParams(c, "REQUEST_METHOD", "POST"); sendParams(c, "CONTENT_LENGTH", "17"); // 17 爲body的長度 !!!! sendParams(c, "CONTENT_TYPE", "application/x-www-form-urlencoded"); sendEndRequestRecord(c); /*FCGI_Header makeHeader(int type, int requestId, int contentLength, int paddingLength)*/ //設置type==5,爲了發 body FCGI_Header t = makeHeader(FCGI_STDIN, c->requestId_, 17, 0); // 17 爲body的長度 !!!! send(c->sockfd_, &t, sizeof(t), 0); /*發送正式的 body */ send(c->sockfd_, "a=20&b=10&c=5&d=6", 17, 0); // 17 爲body的長度 !!!! //製造頭告訴 body 結束 FCGI_Header endHeader; endHeader = makeHeader(FCGI_STDIN, c->requestId_, 0, 0); send(c->sockfd_, &endHeader, sizeof(endHeader), 0); printf("end-----\n"); readFromPhp(c); FastCgi_finit(c); return 0; }
Operation.php文件
<html> <body> <?php #預約義的 $_REQUEST 變量包含了 $_GET、$_POST 和 $_COOKIE 的內容。 #$_REQUEST 變量可用來收集經過 GET 和 POST 方法發送的表單數據。 $a=$_REQUEST["a"]; $b=$_REQUEST["b"]; $c=$_REQUEST["c"]; $d=$_REQUEST["d"]; $result =($a-$b)+($c*$d); echo $a.' - '.$b. ' + ' .$c. ' * ' .$d. " = $result" // echo '1'; // var_dump($_REQUEST); // echo $a; ?> </body> </html>
運行截圖:
見:csdn1
#include <stdio.h> #include <stdlib.h> #include "fcgi.h" #include <sys/types.h> #include <sys/socket.h> int main() { FastCgi_t *c; c = (FastCgi_t *)malloc(sizeof(FastCgi_t)); FastCgi_init(c); setRequestId(c, 1); /*1 用來代表此消息爲請求開始的第一個消息*/ startConnect(c); sendStartRequestRecord(c); sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php"); sendParams(c, "REQUEST_METHOD", "GET"); sendParams(c, "CONTENT_LENGTH", "0"); // 0 表示沒有 body sendParams(c, "CONTENT_TYPE", "text/html"); sendParams(c, "QUERY_STRING", "a=20&b=10&c=5&d=6"); sendEndRequestRecord(c); //告訴cgi程序 head 有多長 /* int sendEndRequestRecord(FastCgi_t *c) { int rc; FCGI_Header endHeader; endHeader = makeHeader(FCGI_PARAMS, c->requestId_, 0, 0); rc = write(c->sockfd_, (char *)&endHeader, FCGI_HEADER_LEN); assert(rc == FCGI_HEADER_LEN); return 1; } */ printf("end-----\n"); readFromPhp(c); FastCgi_finit(c); return 0; }
運行截圖:
須要注意的是查詢字符串(QUERY_STRING
)必須放在sendEndRequestRecord(c);
函數以前,想想http
協議是怎樣處理帶參數的get
就要知道了.....
type | record |
---|---|
1 | header(消息頭) + 開始請求體(8字節) |
3 | header + 結束請求體(8字節) |
4 | header + name-value長度(8字節) + 具體的name-value |
5,6,7 | header + 具體內容 |
最後,附上個人webserver
項目地址,裏邊含有使用到的fastcgi
庫.求star,求fork,哈哈哈哈...