C語言Tinyhttpd服務(wù)器源碼剖析
簡介
Tinyhttpd是一個不到500行的簡單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 httpdMakefile非常簡單,定義了兩個編譯目標(biāo)httpd(服務(wù)器程序)、客戶端程序。
startup函數(shù)
這個函數(shù)意圖比較明顯,就是根據(jù)提供的端口號顯示創(chuàng)建listen fd,而該listen fd是阻塞的。目前對SO_REUSEADDR選項還不是很清楚。
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選項。
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");
}
// 端口號為0,那么就動態(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
這個函數(shù)用來查看OS動態(tài)給socket分配的端口信息等。
accept_request
創(chuàng)建listen fd之后,然后程序直接在本進程中accept,創(chuàng)建accept fd,然后來處理HTTP 請求。
void accept_request(void *arg) {
// intptr_t 什么時候用到不是很清楚。
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沒有實現(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

因為fafafafa是亂輸入的,所以不支持該方法,上述是httpd返回的值。其執(zhí)行流程如下:

下面是一個完整是GET一個完整的HTTP報文頭部字段,返回的是htocs下的index文件信息。

execute_cgi 解析
在POST請求下,或者是GET請求,但有查詢參數(shù)或請求資源為可執(zhí)行程序下,execute_cgi將會被調(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) {
// 丟保其它報文頭部字段
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];
//子進程STDOUT重定向到管道1的寫端中。
dup2(cgi_output[1], STDOUT);
// 子進程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 */
// 父進程關(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);
// 向子進程一個字節(jié)一個字節(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)

- 在子進程中,把 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 運行 cgi 程序。
- 在父進程中,關(guān)閉 cgi_input 的讀取端 和 cgi_output 的寫入端,如果 POST 的話,把 POST 數(shù)據(jù)寫入 cgi_input,已被重定向到 STDIN,讀取 cgi_output 的管道輸出到客戶端,該管道輸入是 STDOUT。接著關(guān)閉所有管道,等待子進程結(jié)束。這一部分比較亂,見下圖說明:
這里利用的是exec默認(rèn)的輸入和輸出為STDIN和STDOUT,如果講STDIN重定向后,那么CGI腳本將從cgi_input管道中讀,執(zhí)行完后的結(jié)果寫到cgi_out管道中,然后父進程讀取返回給客戶端。
接口學(xué)習(xí)
getsockname用來獲取OS給自己綁定的端口信息等。
stat用來查看文件的屬性,是普通文件還是可執(zhí)行文件
recv用來獲取socket消息
send 用來將消息發(fā)送給協(xié)議棧
dup2 用一個新的文件描述符來復(fù)制一個舊的文件描述符,這樣兩個文件描述符共享同樣的文件狀態(tài)。這里的代碼用dup2將管道和標(biāo)準(zhǔn)輸入和輸出聯(lián)系一起。

pipe生產(chǎn)管道
以上就是C語言Tinyhttpd服務(wù)器源碼剖析的詳細內(nèi)容,更多關(guān)于C語言Tinyhttpd服務(wù)器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言 數(shù)據(jù)結(jié)構(gòu)之鏈表實現(xiàn)代碼
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)之鏈表實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10
C語言實現(xiàn)ATM自動取款機系統(tǒng)的示例代碼
ATM自動取款機系統(tǒng)是銀行業(yè)務(wù)流程中十分重要且必備的環(huán)節(jié)之一,在銀行業(yè)務(wù)流程中起著承上啟下的作用。本文將用C語言實現(xiàn)一個簡單的ATM自動取款機系統(tǒng),需要的可以參考一下2022-08-08
海量數(shù)據(jù)處理系列之:用C++實現(xiàn)Bitmap算法
本篇文章是對用C++實現(xiàn)Bitmap算法進行了詳細的分析介紹,需要的朋友參考下2013-05-05
C++分析構(gòu)造函數(shù)與析造函數(shù)的特點梳理
本文對類的構(gòu)造函數(shù)和析構(gòu)函數(shù)進行總結(jié),主要包括了構(gòu)造函數(shù)的初始化、重載、使用參數(shù)和默認(rèn)參數(shù),拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù),希望能幫助讀者在程序開發(fā)中更好的理解類,屬于C/C++基礎(chǔ)2022-05-05

