本文介紹的是一個單進程,支持PHP辅斟,能夠處理GET一忱、POST請求的web服務(wù)器听皿,項目地址為:https://github.com/jaykizhou/php-server/
程序大致流程圖如下:
web服務(wù)器在指定端口等待用戶請求連接徐紧,當(dāng)有連接請求時静檬,執(zhí)行的是server.c中的doit函數(shù)。
36~42行并级,讀取請求行拂檩,分別提取請求方法、請求uri和HTTP協(xié)議版本嘲碧。
45~49行稻励,判斷請求方法是否是GET或POST,如果不是愈涩,則向客戶端發(fā)送提示信息望抽。
52行加矛,讀取請求頭,將頭部數(shù)據(jù)保存到struct http_header結(jié)構(gòu)體中煤篙。
55行斟览,分析請求uri,計算請求文件的絕對路徑和查詢參數(shù)舰蟆,并保存到struct http_header
結(jié)構(gòu)體中。
struct http_header
結(jié)構(gòu)體聲明如下:
struct http_header {
char uri[256]; // 請求地址
char method[16]; // 請求方法
char version[16]; // 協(xié)議版本
char filename[256]; // 請求文件名(包含完整路徑)
char name[256]; // 請求文件名(不包含路徑狸棍,只有文件名)
char cgiargs[256]; // 查詢參數(shù)
char contype[256]; // 請求體類型
char conlength[16]; // 請求體長度
};
58~62行身害,判斷請求文件是否存在,如果不存在草戈,則向客戶端發(fā)送提示信息塌鸯。
64~71行,如果請求文件是靜態(tài)資源唐片,并且有讀取權(quán)限丙猬,則直接讀取文件內(nèi)容發(fā)送給客戶端,具體實現(xiàn)見server.c中的serve_static函數(shù)费韭,不再詳述茧球。否則,向客戶端發(fā)送沒有權(quán)限提示信息星持。
72~79行抢埋,如果請求文件是動態(tài)php文件,并且有執(zhí)行權(quán)限督暂,則調(diào)用serve_dynamic函數(shù)揪垄,否則,向客戶端發(fā)送沒有權(quán)限提示信息逻翁。
serve_dynamic函數(shù)通過與php-fpm通信來處理php文件饥努。web服務(wù)器本身并沒有執(zhí)行php文件的能力,需要由專門的php解釋器執(zhí)行八回。而php-fpm就是一個php解釋器酷愧,只要將php-fpm需要的參數(shù)(具體的參數(shù)見下面說明)發(fā)送過去,然后讀取php-fpm的執(zhí)行結(jié)果發(fā)送給客戶端缠诅。
243行伟墙,創(chuàng)建一個套接字,連接php fastcgi服務(wù)器滴铅,即php-fpm戳葵。
246行,向php-fpm發(fā)送請求數(shù)據(jù)汉匙,包含請求文件名拱烁、請求方法生蚁、查詢參數(shù)等。
249行戏自,讀取php-fpm處理結(jié)果邦投,并發(fā)送給客戶端。
對于fastcgi和php-fpm之間的關(guān)系擅笔,可參見:https://segmentfault.com/q/1010000000256516
send_fastcgi函數(shù)主要通過fastcgi協(xié)議規(guī)范規(guī)定的消息格式志衣,發(fā)送數(shù)據(jù)給php-fpm。
337~345行猛们,向php-fpm發(fā)送的各種param參數(shù)念脯。
348~356行,對應(yīng)上面參數(shù)在struct http_header結(jié)構(gòu)體中的偏移位置弯淘。
359~362行绿店,向php-fpm發(fā)送請求開始記錄,表示開始請求庐橙。
365376行假勿,向php-fpm發(fā)送337345行處定義的各個param參數(shù)。
379~382行态鳖,向php-fpm發(fā)送空的param參數(shù)記錄转培,這是fastcgi協(xié)議規(guī)定,必須在具體param參數(shù)發(fā)送完畢后浆竭,發(fā)送一個內(nèi)容為空的param參數(shù)堡距。
385~403行,向php-fpm發(fā)送stdin數(shù)據(jù)兆蕉,只有是post請求羽戒,并且有請求體數(shù)據(jù)時才會執(zhí)行。首先讀取請求體虎韵,然后發(fā)送給php-fpm易稠。
406~409行,向php-fpm發(fā)送內(nèi)容為空的stdin數(shù)據(jù)包蓝,同param參數(shù)驶社,這是fastcgi協(xié)議規(guī)定。
Fastcgi協(xié)議定義了web服務(wù)器與php-fpm通信消息格式测萎。每條消息包含消息頭和消息體亡电,消息頭是一些元數(shù)據(jù)信息,用C語言表示如下:
/*
* fastcgi協(xié)議報頭
*/
typedef struct {
unsigned char version; // 版本
unsigned char type; // 協(xié)議記錄類型
unsigned char requestIdB1; // 請求ID
unsigned char requestIdB0;
unsigned char contentLengthB1; // 內(nèi)容長度
unsigned char contentLengthB0;
unsigned char paddingLength; // 填充字節(jié)長度
unsigned char reserved; // 保留字節(jié)
} FCGI_Header;
version字段:標(biāo)識Fastcgi協(xié)議版本硅瞧,默認(rèn)為1份乒。
type字段:標(biāo)識Fastcgi協(xié)議記錄類型,比如開始請求記錄類型、param參數(shù)記錄類型或辖、stdin數(shù)據(jù)記錄類型等瘾英。
requestId字段:標(biāo)識該條連接線路。requestId=requestIdB1<<8 + requestIdB0颂暇。由于結(jié)構(gòu)體中每個字段類型為unsigned char類型缺谴,一個字節(jié),當(dāng)requestId的值大于一個字節(jié)的值耳鸯,需要將值分開放在相鄰的B1和B0中湿蛔,下面contentLength同理。
contentLength字段:消息體中contentData的字節(jié)數(shù)县爬。
paddingLength字段:消息體中paddingData的字節(jié)數(shù)阳啥。
reserved字段:暫時未用到。
消息體根據(jù)不同的記錄類型捌省,格式也不一樣苫纤。
1.開始請求記錄類型消息體格式C語言表示如下:
/*
* 請求開始記錄的協(xié)議體
*/
typedef struct {
unsigned char roleB1; // web服務(wù)器期望php-fpm扮演的角色
unsigned char roleB0;
unsigned char flags; // 控制連接響應(yīng)后是否立即關(guān)閉
unsigned char reserved[5];
} FCGI_BeginRequestBody;
role字段:期望php-fpm扮演的角色碉钠,一般是響應(yīng)器FCGI_RESPONDER纲缓。
flags字段:如果為0,表示請求結(jié)束后關(guān)閉該連接線路喊废,否則不關(guān)閉祝高。
2.結(jié)束請求記錄類型消息體格式C語言表示如下:
/*
* 結(jié)束請求記錄的協(xié)議體
*/
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus; // 協(xié)議級別的狀態(tài)碼
unsigned char reserved[3];
} FCGI_EndRequestBody;
appStatus字段:應(yīng)用級別的狀態(tài)碼。
protocolStatus字段:協(xié)議級別的狀態(tài)碼污筷,可能的值有:
FCGI_REQUEST_COMPLETE:請求的正常結(jié)束工闺。
FCGI_CANT_MPX_CONN:拒絕新請求。
FCGI_OVERLOADED:拒絕新請求瓣蛀。
FCGI_UNKNOWN_ROLE:拒絕新請求陆蟆。
3.字節(jié)流(FCGI_STDIN、FCGI_STDOUT惋增、FCGI_STDERR)記錄類型消息格式C語言表示如下:
typedef struct {
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Body;
contentData字段:具體消息數(shù)據(jù)叠殷,contentLength值已消息頭結(jié)構(gòu)體中設(shè)置。
paddingData字段:填充數(shù)據(jù)诈皿,直接丟棄林束。由于協(xié)議規(guī)定,消息以8字節(jié)對齊稽亏,這樣可以提高網(wǎng)絡(luò)通信效率壶冒,所以當(dāng)contentData部分?jǐn)?shù)據(jù)不滿足8字節(jié)對齊,需要填充數(shù)據(jù)截歉。
4.名-值對流(FCGI_PARAMS)記錄類型消息格式C語言表示如下:
typedef struct {
unsigned char nameLength;
unsigned char valueLength;
unsigned char data[0];
} FCGI_ParamsBody;
nameLength字段:param參數(shù)名的字節(jié)數(shù)胖腾。
valueLength字段:param參數(shù)值的字節(jié)數(shù)。
data字段:依次是名和值的具體數(shù)據(jù)。
詳細(xì)的fastcgi規(guī)范可參見:http://andylin02.iteye.com/blog/648412/胸嘁,實現(xiàn)代碼參見fastcgi.c文件瓶摆。
另外,rio.c參照《CSAPP》一書第10章性宏,主要是對系統(tǒng)read群井、write的包裝。