您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)怎么用C寫一個(gè)web服務(wù)器之基礎(chǔ)功能的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
以 nginx 的思想來考慮本服務(wù)器架構(gòu),初步考慮如下圖:
當(dāng)然 php 進(jìn)程也可以替換為其他的腳本語言,可以更改源碼中的 command 變量實(shí)現(xiàn)。
服務(wù)器有一個(gè) master 進(jìn)程,其有多個(gè)子進(jìn)程為 worker 進(jìn)程,master 進(jìn)程受理客戶端的請(qǐng)求,然后分發(fā)給 worker 進(jìn)程,worker 進(jìn)程處理 http 頭信息后將參數(shù)傳遞給 php 進(jìn)程處理后,將結(jié)果返回到上層,再響應(yīng)給客戶端。
也考慮過使用 php-fpm 的 worker 進(jìn)程池方式,那樣的話 php-fpm 進(jìn)程也要仿寫了,目前還不熟悉其內(nèi)部構(gòu)造,如果可以簡(jiǎn)單化,自然向其靠攏。目前對(duì) PHP 的 SAPI 接口不熟,了解一下再考慮。
當(dāng)前狀態(tài)的服務(wù)器還極其簡(jiǎn)單,總結(jié)下來有以下地方待優(yōu)化:
當(dāng)前還是單進(jìn)程,需要改成多進(jìn)程,最終為 worker 進(jìn)程池方式;
優(yōu)化 socket IO 模型,考慮 epoll、事件驅(qū)動(dòng)方式;
只支持 HTTP GET 請(qǐng)求方法,未進(jìn)行太多的異常處理來定義 http 狀態(tài)碼;
與 php 進(jìn)程的交互方式,考慮如 nginx 使用 unix domain socket 方式。
協(xié)議目前只考慮了 http,后續(xù)會(huì)考慮一些基于 TCP 的協(xié)議;
雖然簡(jiǎn)單,但服務(wù)器已經(jīng)有基本的功能了:
它監(jiān)聽本地地址的 8080 端口,將接收到的 http 頭中的 path 信息提出出來交給 php 進(jìn)程,php 進(jìn)程將參數(shù)信息處理后返回給服務(wù)器,服務(wù)器拼裝 http 響應(yīng)信息再將結(jié)果返回給客戶端。
下面介紹各個(gè)功能的實(shí)現(xiàn):
在介紹函數(shù)之間先用一張圖來介紹一次 http 請(qǐng)求中客戶端與服務(wù)器之間的交互:
如圖:服務(wù)器創(chuàng)建要進(jìn)行:
1.調(diào)用 socket() 創(chuàng)建一個(gè)連接;int socket(int domain, int type, int protocol);
2.調(diào)用 bind() 給套接字命名,綁定端口;int bind( int socket, const struct sockaddr *address, size_t address_len);
3.調(diào)用 listen() 監(jiān)聽此套接字;int listen(int socket, int backlog);
4.調(diào)用 accept() 接受客戶端的連接;int accept(int socket, struct sockaddr *address, size_t *address_len);
5.調(diào)用 recv() 接收客戶端的信息;int recv(int s, void *buf, int len, unsigned int flags);
6.調(diào)用 send() 將響應(yīng)信息發(fā)送給客戶端;int send(int s, const void * msg, int len, unsigned int falgs);
socket 間的接收和發(fā)送信息在 C 中有幾個(gè)系列:write() / read() 、send() / recv() 、sendto() / recvfrom()、 sendmsg() / recvmsg(),可以自行選用。
另外函數(shù)參數(shù)釋義和要點(diǎn),都被我注釋在代碼中了,感興趣的可以拉下來看一下,這些在網(wǎng)上也多有介紹,這里不再贅述。
然后是 C 進(jìn)程和 php 進(jìn)程的交互,考慮到簡(jiǎn)單易用,目前在 C 進(jìn)程中直接執(zhí)行 php 腳本:
一開始使用 system() 函數(shù): int system(const char *command);
system 函數(shù)會(huì) fork 一個(gè)子進(jìn)程,在子進(jìn)程中以 cli 方式執(zhí)行 php 腳本,并將錯(cuò)誤碼或返回值返回。由于其結(jié)果類型不可控,編譯時(shí)會(huì)報(bào)一個(gè) warning。而且它將結(jié)果返回給父進(jìn)程時(shí),還會(huì)在標(biāo)準(zhǔn)輸出中打印結(jié)果,在服務(wù)器執(zhí)行時(shí)會(huì)拋出異常。
于是找到了另一個(gè)方法 popen, FILE * popen(const char * command, const char * type);:
popen 同樣會(huì) fork 一個(gè)子進(jìn)程來執(zhí)行 command ,然后建立管道連到子進(jìn)程的標(biāo)準(zhǔn)輸出設(shè)備或標(biāo)準(zhǔn)輸入設(shè)備,然后返回一個(gè)文件指針。隨后進(jìn)程便可利用此文件指針來讀取子進(jìn)程的輸出設(shè)備或是寫入到子進(jìn)程的標(biāo)準(zhǔn)輸入設(shè)備中。
其 type 參數(shù)便是控制連接到子進(jìn)程的標(biāo)準(zhǔn)輸入還是標(biāo)準(zhǔn)輸出。我們想要子進(jìn)程的標(biāo)準(zhǔn)輸出,于是傳入 type參數(shù)為 字符 “r” (read)。同理,如果想寫入子進(jìn)程標(biāo)準(zhǔn)輸入的話,可以傳值 “w”(write)。
另外在接收緩沖區(qū)內(nèi)容的時(shí)候也出現(xiàn)了一點(diǎn)小意外:由于使用的 fgets() 方法會(huì)以換行符\n
為一段的結(jié)尾,在接收 php 進(jìn)程輸出時(shí)遇到換行會(huì)結(jié)束,這里使用了一個(gè)中間字符串?dāng)?shù)組line
來接收每一行的信息,將每一行的信息拼裝到結(jié)果中。
代碼如下:
char * execPHP(char *args){ // 這里不能用變長(zhǎng)數(shù)組,需要給command留下足夠長(zhǎng)的空間,以存儲(chǔ)args參數(shù),不然拼接參數(shù)時(shí)會(huì)棧溢出 char command[BUFF_SIZE] = "php /Users/mfhj-dz-001-441/CLionProjects/cproject/tinyServer/index.php "; FILE *fp; static char buff[BUFF_SIZE]; // 聲明靜態(tài)變量以返回變量指針地址 char line[BUFF_SIZE]; strcat(command, args); memset(buff, 0, BUFF_SIZE); // 靜態(tài)變量會(huì)一直保留,這里初始化一下 if((fp = popen(command, "r")) == NULL){ strcpy(buff, "服務(wù)器內(nèi)部錯(cuò)誤"); }else{ // fgets會(huì)在獲取到換行時(shí)停止,這里將每一行拼接起來 while (fgets(line, BUFF_SIZE, fp) != NULL){ strcat(buff, line); }; } return buff; }
socket 處于應(yīng)用層和傳輸層之間的虛擬層,由于設(shè)置服務(wù)器 socket 協(xié)議類型為 TCP,那么 TCP 的握手揮手、數(shù)據(jù)讀取等步驟對(duì)于我們都是透明的。我們拿到的數(shù)據(jù)即 HTTP 報(bào)文,關(guān)于 HTTP 報(bào)文結(jié)構(gòu)和其字段解釋的文章非常多,這里也不再多提。
首先使用 C 的 strtok() 方法,獲取到 HTTP 頭的第一行,獲取到其 http 方法和 path 信息,將這些信息處理后,再使用 sprintf() 方法拼合 HTTP 響應(yīng)報(bào)文,主要替換了 響應(yīng)內(nèi)容長(zhǎng)度和響應(yīng)內(nèi)容。
感謝各位的閱讀!關(guān)于“怎么用C寫一個(gè)web服務(wù)器之基礎(chǔ)功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。