C語言Tinyhttpd服務(wù)器源碼剖析
簡(jiǎn)介
Tinyhttpd是一個(gè)不到500行的簡(jiǎn)單http服務(wù)器。
Makefile解析
all: httpd client LIBS = -lpthread #-lsocket httpd: httpd.c gcc -g -W -Wall $(LIBS) -o $@ $< client: simpleclient.c gcc -W -Wall -o $@ $< clean: rm httpd
Makefile非常簡(jiǎn)單,定義了兩個(gè)編譯目標(biāo)httpd(服務(wù)器程序)、客戶端程序。
startup函數(shù)
這個(gè)函數(shù)意圖比較明顯,就是根據(jù)提供的端口號(hào)顯示創(chuàng)建listen fd,而該listen fd是阻塞的。目前對(duì)SO_REUSEADDR選項(xiàng)還不是很清楚。
int startup(u_short port) { int httpd = 0; int on = 1; struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, 0); if (httpd == -1) error_die("socket"); memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); // 將套接字設(shè)置SO_REUSEADDR選項(xiàng)。 if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) { error_die("setsockopt failed"); } if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) { error_die("bind"); } // 端口號(hào)為0,那么就動(dòng)態(tài)的分配端口 if (*port == 0) { socklen_t namelen = sizeof(name); if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) error_die("getsockname"); *port = ntohs(name.sin_port); } if (listen(httpd, 5) < 0) error_die("listen"); return(httpd); }
getsockname
這個(gè)函數(shù)用來查看OS動(dòng)態(tài)給socket分配的端口信息等。
accept_request
創(chuàng)建listen fd之后,然后程序直接在本進(jìn)程中accept,創(chuàng)建accept fd,然后來處理HTTP 請(qǐng)求。
void accept_request(void *arg) { // intptr_t 什么時(shí)候用到不是很清楚。 int client = (intptr_t)arg; // bug 解決:client值無效 client = *(int *) arg char buf[1024]; size_t numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; int cgi = 0; char *query_string = NULL; // 讀取當(dāng)前客戶端發(fā)送而來的一行 numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; // 首先獲取方法名稱 while (!ISspace(buf[i]) && (i < sizeof(method) - 1)) { method[i] = buf[i]; i++; } j=i; method[i] = '\0'; // 如果不是GET 方法那么就直接返回GET沒有實(shí)現(xiàn) // 直接返回 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); return; } // 如果是POST方法那么 if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; while (ISspace(buf[j]) && (j < numchars)) j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) { url[i] = buf[j]; i++; j++; } // url[i] = '\0'; if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if (*query_string == '?') { cgi = 1; *query_string = '\0'; query_string++; } } sprintf(path, "htdocs%s", url); if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); if (stat(path, &st) == -1) { while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); not_found(client); } else { if ((st.st_mode & S_IFMT) == S_IFDIR) strcat(path, "/index.html"); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1; if (!cgi) serve_file(client, path); else // 執(zhí)行CGI腳本 execute_cgi(client, path, method, query_string); } close(client); }
我們通過nc來調(diào)試 nc 127.0.0.1 4000
因?yàn)閒afafafa是亂輸入的,所以不支持該方法,上述是httpd返回的值。其執(zhí)行流程如下:
下面是一個(gè)完整是GET一個(gè)完整的HTTP報(bào)文頭部字段,返回的是htocs下的index文件信息。
execute_cgi 解析
在POST請(qǐng)求下,或者是GET請(qǐng)求,但有查詢參數(shù)或請(qǐng)求資源為可執(zhí)行程序下,execute_cgi將會(huì)被調(diào)用。
void execute_cgi(int client, const char *path, const char *method, const char *query_string) { char buf[1024]; int cgi_output[2]; int cgi_input[2]; pid_t pid; int status; int i; char c; int numchars = 1; int content_length = -1; buf[0] = 'A'; buf[1] = '\0'; if (strcasecmp(method, "GET") == 0) { // 丟保其它報(bào)文頭部字段 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); } else if (strcasecmp(method, "POST") == 0) { numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)){ buf[15] = '\0'; if (strcasecmp(buf, "Content-Length:") == 0) content_length = atoi(&(buf[16])); numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) { bad_request(client); return; } } else/*HEAD or other*/ { } if (pipe(cgi_output) < 0) { cannot_execute(client); return; } if (pipe(cgi_input) < 0) { cannot_execute(client); return; } if ( (pid = fork()) < 0 ) { cannot_execute(client); return; } sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); if (pid == 0) /* child: CGI script */ { char meth_env[255]; char query_env[255]; char length_env[255]; //子進(jìn)程STDOUT重定向到管道1的寫端中。 dup2(cgi_output[1], STDOUT); // 子進(jìn)程STDIN重定向管道0的讀端到中。 dup2(cgi_input[0], STDIN); // 關(guān)掉其它不用的一端 close(cgi_output[0]); close(cgi_input[1]); sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST */ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } // 執(zhí)行可執(zhí)行程序。 execl(path, NULL); exit(0); } else { /* parent */ // 父進(jìn)程關(guān)掉不用的一端 close(cgi_output[1]); close(cgi_input[0]); if (strcasecmp(method, "POST") == 0) for (i = 0; i < content_length; i++) { recv(client, &c, 1, 0); // 向子進(jìn)程一個(gè)字節(jié)一個(gè)字節(jié)的寫。 write(cgi_input[1], &c, 1); } // 從輸出管道中讀入執(zhí)行結(jié)果后發(fā)送給客戶端。 while (read(cgi_output[0], &c, 1) > 0) send(client, &c, 1, 0); close(cgi_output[0]); close(cgi_input[1]); waitpid(pid, &status, 0); } }
管道的初始狀態(tài):
管道最終狀態(tài)
- 在子進(jìn)程中,把 STDOUT 重定向到 cgi_output 的寫入端,把 STDIN 重定向到 cgi_input 的讀取端,關(guān)閉 cgi_input 的寫入端 和 cgi_output 的讀取端,設(shè)置 request_method 的環(huán)境變量,GET 的話設(shè)置 query_string 的環(huán)境變量,POST 的話設(shè)置 content_length 的環(huán)境變量,這些環(huán)境變量都是為了給 cgi 腳本調(diào)用,接著用 execl 運(yùn)行 cgi 程序。
- 在父進(jìn)程中,關(guān)閉 cgi_input 的讀取端 和 cgi_output 的寫入端,如果 POST 的話,把 POST 數(shù)據(jù)寫入 cgi_input,已被重定向到 STDIN,讀取 cgi_output 的管道輸出到客戶端,該管道輸入是 STDOUT。接著關(guān)閉所有管道,等待子進(jìn)程結(jié)束。這一部分比較亂,見下圖說明:
這里利用的是exec默認(rèn)的輸入和輸出為STDIN和STDOUT,如果講STDIN重定向后,那么CGI腳本將從cgi_input管道中讀,執(zhí)行完后的結(jié)果寫到cgi_out管道中,然后父進(jìn)程讀取返回給客戶端。
接口學(xué)習(xí)
getsockname用來獲取OS給自己綁定的端口信息等。
stat用來查看文件的屬性,是普通文件還是可執(zhí)行文件
recv用來獲取socket消息
send 用來將消息發(fā)送給協(xié)議棧
dup2 用一個(gè)新的文件描述符來復(fù)制一個(gè)舊的文件描述符,這樣兩個(gè)文件描述符共享同樣的文件狀態(tài)。這里的代碼用dup2將管道和標(biāo)準(zhǔn)輸入和輸出聯(lián)系一起。
pipe生產(chǎn)管道
以上就是C語言Tinyhttpd服務(wù)器源碼剖析的詳細(xì)內(nèi)容,更多關(guān)于C語言Tinyhttpd服務(wù)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C/C++ 中sizeof(''a'')對(duì)比詳細(xì)介紹
這篇文章主要介紹了C/C++ 中sizeof('a')的值對(duì)比詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-02-02C語言 數(shù)據(jù)結(jié)構(gòu)之鏈表實(shí)現(xiàn)代碼
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)之鏈表實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10C語言實(shí)現(xiàn)ATM自動(dòng)取款機(jī)系統(tǒng)的示例代碼
ATM自動(dòng)取款機(jī)系統(tǒng)是銀行業(yè)務(wù)流程中十分重要且必備的環(huán)節(jié)之一,在銀行業(yè)務(wù)流程中起著承上啟下的作用。本文將用C語言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ATM自動(dòng)取款機(jī)系統(tǒng),需要的可以參考一下2022-08-08海量數(shù)據(jù)處理系列之:用C++實(shí)現(xiàn)Bitmap算法
本篇文章是對(duì)用C++實(shí)現(xiàn)Bitmap算法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++分析構(gòu)造函數(shù)與析造函數(shù)的特點(diǎn)梳理
本文對(duì)類的構(gòu)造函數(shù)和析構(gòu)函數(shù)進(jìn)行總結(jié),主要包括了構(gòu)造函數(shù)的初始化、重載、使用參數(shù)和默認(rèn)參數(shù),拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù),希望能幫助讀者在程序開發(fā)中更好的理解類,屬于C/C++基礎(chǔ)2022-05-05