自己動手實現(xiàn)一個web服務(wù)器(支持PHP)

本文介紹的是一個單進程,支持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的包裝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毫胜,一起剝皮案震驚了整個濱河市书斜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酵使,老刑警劉巖荐吉,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異口渔,居然都是意外死亡样屠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門缺脉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痪欲,“玉大人,你說我怎么就攤上這事攻礼∫堤撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵礁扮,是天一觀的道長知举。 經(jīng)常有香客問我,道長太伊,這世上最難降的妖魔是什么雇锡? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮僚焦,結(jié)果婚禮上锰提,老公的妹妹穿的比我還像新娘。我一直安慰自己叠赐,他們只是感情好欲账,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芭概,像睡著了一般赛不。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罢洲,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天踢故,我揣著相機與錄音文黎,去河邊找鬼。 笑死殿较,一個胖子當(dāng)著我的面吹牛耸峭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淋纲,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼劳闹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了洽瞬?” 一聲冷哼從身側(cè)響起本涕,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伙窃,沒想到半個月后菩颖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡为障,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年晦闰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鳍怨。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡呻右,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出京景,到底是詐尸還是另有隱情窿冯,我是刑警寧澤骗奖,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布确徙,位于F島的核電站,受9級特大地震影響执桌,放射性物質(zhì)發(fā)生泄漏鄙皇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一仰挣、第九天 我趴在偏房一處隱蔽的房頂上張望伴逸。 院中可真熱鬧,春花似錦膘壶、人聲如沸错蝴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顷锰。三九已至,卻和暖如春亡问,著一層夾襖步出監(jiān)牢的瞬間官紫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留束世,地道東北人酝陈。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像毁涉,于是被迫代替她去往敵國和親沉帮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容