欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語言Tinyhttpd服務(wù)器源碼剖析

 更新時(shí)間:2023年09月08日 09:54:43   作者:BruceChen7  
這篇文章主要為大家介紹了C語言Tinyhttpd服務(wù)器源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡(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)文章

最新評(píng)論