日本搞逼视频_黄色一级片免费在线观看_色99久久_性明星video另类hd_欧美77_综合在线视频

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > php教程 > Tinyhttp源碼分析

Tinyhttp源碼分析

來源:程序員人生   發布時間:2016-06-08 08:38:05 閱讀次數:3045次

Tinyhttp源碼分析

  • 簡介

    Tinyhttp是1個輕量型Http Server,使用C語言開發,全部代碼只500多行,還包括1個簡單Client。

  • 源碼剖析

    Tinyhttp程序的邏輯為:1個無線循環,1個要求,創建1個線程,以后線程函數處理每一個要求,然后解析HTTP要求,做1些判斷,以后判斷文件是不是可履行,不可履行,打開文件,輸出給客戶端(閱讀器),可履行就創建管道,父子進程進行通訊。其整體處理流程以下:

    每一個函數的作用以下:

    // accept_request函數:處理從套接字上監聽到的1個HTTP要求,此函數很大部份體現服務器處理要求流程。
    void accept_request(void *);
    // bad_request函數:返回給客戶端這是個毛病要求,HTTP狀態碼400 Bad Request。
    void bad_request(int);
    // cat函數:讀取服務器上某個文件寫到socket套接字。
    void cat(int, FILE *);
    // cannot_execute函數:處理產生在履行cgi程序時出現的毛病。
    void cannot_execute(int);
    // error_die函數:把毛病信息寫到perror并退出。
    void error_die(const char *);
    // execute_cgi函數:運行cgi程序的處理,是主要的函數。
    void execute_cgi(int, const char *, const char *, const char *);
    // get_line函數:讀取套接字的1行,把回車換行等情況都統1為換行符結束。
    int get_line(int, char *, int);
    // headers函數:把HTTP響應的頭部寫到套接字。
    void headers(int, const char *);
    // not_found函數:處理找不到要求的文件時的情況。
    void not_found(int);
    // serve_file函數:調用cat函數把服務器文件返回給閱讀器
    void serve_file(int, const char *);
    // startup函數:初始化httpd服務,包括建立套接字,綁定端口,進行監聽等。
    int startup(u_short *);
    // unimplemented函數:返回給閱讀器表明收到的HTTP要求所用的method不被支持。
    void unimplemented(int);
    

    分析其程序,流程為:main()——>startup()——>accept_request()——>execute_cgi()等。

  • 核心函數

    1)main()函數

    // 服務器main函數
    int main(void)
    {
        int server_sock = ⑴;
        u_short port = 4000;
        int client_sock = ⑴;
        struct sockaddr_in client_name;
        socklen_t  client_name_len = sizeof(client_name);
        pthread_t newthread;
    
        // 建立1個監聽套接字,在對應的端口建立httpd服務
        server_sock = startup(&port);
        printf("httpd running on port %d\n", port);
        // 進入循環,服務器通過調用accept等待客戶真個連接,Accept會以阻塞的方式運行,直到
        // 有客戶端連接才會返回。連接成功后,服務器啟動1個新的線程來處理客戶真個要求,處理
        // 完成后,重新等待新的客戶端要求。
        while (1)
        {
            // 返回1個已連接套接字,套接字收到客戶端連接要求
            client_sock = accept(server_sock,
                    (struct sockaddr *)&client_name,
                    &client_name_len);
            if (client_sock == ⑴)
                error_die("accept");
            // 派生線程用accept_request函數處理新要求。
            /* accept_request(client_sock); */
            if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock) != 0)
                perror("pthread_create");
        }
        // 出現意外退出的時候,關閉socket
        close(server_sock);
    
        return(0);
    }
    

    2)startup()函數

    // startup函數:依照TCP連接的正常流程順次調用socket,bind,listen函數。
    // 監聽套接字端口既可以指定也能夠動態分配1個隨機端口
    int startup(u_short *port)
    {
        int httpd = 0;
        struct sockaddr_in name;
        // 創建1個socket,建立socket連接
        httpd = socket(PF_INET, SOCK_STREAM, 0);
        if (httpd == ⑴)
            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);
        // 將socket綁定到對應的端口上
        if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
            error_die("bind");
        // 如果當前指定的端口是0,則動態隨機分配1個端口
        if (*port == 0)  /* if dynamically allocating a port */
        {
            socklen_t namelen = sizeof(name);
            // 1.getsockname()可以取得1個與socket相干的地址
            //  1)服務器端可以通過它得到相干客戶端地址
            //  2)客戶端可以得到當前已連接成功的socket的IP和端口
            // 2.在客戶端不進行bind而直接連接服務器時,且客戶端需要知道當前使用哪一個IP地址
            //   進行通訊時比較有用(如多網卡的情況)
            if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == ⑴)
                error_die("getsockname");
            *port = ntohs(name.sin_port);
        }
        // 開始監聽
        if (listen(httpd, 5) < 0)
            error_die("listen");
        // 返回socket id
        return(httpd);
    }
    

    3)accept_request()函數

    // 線程處理函數
    void accept_request(void *arg)
    {
        int client = *(int*)arg;
        char buf[1024];       // 讀取行數據時的緩沖區
        size_t numchars;      // 讀取了多少字符
        char method[255];     // 存儲HTTP要求名稱(字符串)
        char url[255];
        char path[512];
        size_t i, j;
        struct stat st;
        int cgi = 0;      /* becomes true if server decides this is a CGI
                           * program */
        char *query_string = NULL;
    
        // 1個HTTP要求報文由要求行(requestline)、要求頭部(header)、空行和要求數據4個部份
        // 組成,要求行由要求方法字段(get或post)、URL字段和HTTP協議版本字段3個字段組成,它們
        // 用空格分隔。如:GET /index.html HTTP/1.1。
        // 解析要求行,把方法字段保存在method變量中。
        // 讀取HTTP頭第1行:GET/index.php HTTP 1.1
        numchars = get_line(client, buf, sizeof(buf));
        i = 0; j = 0;
    
        // 把客戶真個要求方法存到method數組
        while (!ISspace(buf[i]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[i];
            i++;
        }
        j=i;
        method[i] = '\0';
    
        // 只能辨認get和post
        if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
        {
            unimplemented(client);
            return;
        }
    
        // POST的時候開啟cgi
        if (strcasecmp(method, "POST") == 0)
            cgi = 1;
    
        // 解析并保存要求的URL(如有問號,也包括問號及以后的內容)
        i = 0;
        // 跳過空白字符
        while (ISspace(buf[j]) && (j < numchars))
            j++;
        // 從緩沖區中把URL讀取出來
        while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
        {
            // 存在url
            url[i] = buf[j];
            i++; j++;
        }
        url[i] = '\0'; // 保存URL
    
        // 先處理如果是GET要求的情況
        // 如果是get方法,要求參數和對應的值附加在URL后面,利用1個問號(“?”)代表URL的結
        // 尾與要求參數的開始,傳遞參數長度受限制。如index.jsp?10023,其中10023就是要傳遞
        // 的參數。這段代碼將參數保存在query_string中。
        if (strcasecmp(method, "GET") == 0)
        {
            // 待處理要求為url
            query_string = url;
            // 移動指針,去找GET參數,即?后面的部份
            while ((*query_string != '?') && (*query_string != '\0'))
                query_string++;
            // 如果找到了的話,說明這個要求也需要調用腳本來處理
            // 此時就把要求字符串單獨抽取出來
            // GET方法特點,?后面為參數
            if (*query_string == '?')
            {
                // 開啟cgi
                cgi = 1;
                // query_string指針指向的是真實的要求參數
                *query_string = '\0';
                query_string++;
            }
        }
    
        // 保存有效的url地址并加上要求地址的主頁索引。默許的根目錄是htdocs下
        // 這里是做以下路徑拼接,由于url字符串以'/'開頭,所以不用拼接新的分割符
        // 格式化url到path數組,html文件都早htdocs中
        sprintf(path, "htdocs%s", url);
        // 如果訪問路徑的最后1個字符時'/',就為其補全,即默許訪問index.html
        if (path[strlen(path) - 1] == '/')
            strcat(path, "index.html");
    
        // 訪問要求的文件,如果文件不存在直接返回,如果存在就調用CGI程序來處理
        // 根據路徑找到對應文件
        if (stat(path, &st) == ⑴) {
            // 如果不存在,就把剩下的要求頭從緩沖區中讀出去
            // 把所有headers的信息都拋棄
            while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
                numchars = get_line(client, buf, sizeof(buf));
            // 然后返回1個404毛病,即回應客戶端找不到
            not_found(client);
        }
        else
        {
            // 如果文件存在但卻是個目錄,則繼續拼接路徑,默許訪問這個目錄下的index.html
            if ((st.st_mode & S_IFMT) == S_IFDIR)
                strcat(path, "/index.html");
            // 如果文件具有可履行權限,就履行它
            // 如果需要調用CGI(CGI標志位置1)在調用CGI之前有1段是對用戶權限的判斷,對應
            // 含義以下:S_IXUSR:用戶可以履行
            //          S_IXGRP:組可以履行
            //          S_IXOTH:其它人可以履行
            if ((st.st_mode & S_IXUSR) ||
                    (st.st_mode & S_IXGRP) ||
                    (st.st_mode & S_IXOTH)    )
                cgi = 1;
            // 不是cgi,直接把服務器文件返回,否則履行cgi
            if (!cgi)
                serve_file(client, path);
            else
                execute_cgi(client, path, method, query_string);
        }
    
        // 斷開與客戶真個連接(HTTP特點:無連接)
        close(client);
    }
    

    4)execute_cgi()函數

    此函數履行流程以下:

    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 = ⑴;
    
        // 首先需要根據要求是Get還是Post,來分別進行處理
        buf[0] = 'A'; buf[1] = '\0';
        // 如果是Get,那末就疏忽剩余的要求頭
        if (strcasecmp(method, "GET") == 0)
            // 把所有的HTTP header讀取并拋棄
            while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
                numchars = get_line(client, buf, sizeof(buf));
        // 如果是Post,那末就需要讀出要求長度即Content-Length
        else if (strcasecmp(method, "POST") == 0) /*POST*/
        {
            // 對POST的HTTP要求中找出content_length
            numchars = get_line(client, buf, sizeof(buf));
            while ((numchars > 0) && strcmp("\n", buf))
            {
                // 使用\0進行分割
                buf[15] = '\0';
                // HTTP要求的特點
                if (strcasecmp(buf, "Content-Length:") == 0)
                    content_length = atoi(&(buf[16]));
                numchars = get_line(client, buf, sizeof(buf));
            }
            // 如果要求長度不合法(比如根本就不是數字),那末就報錯,即沒有找到content_length
            if (content_length == ⑴) {
                // 毛病要求
                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;
        }
    
        // fork本身,生成兩個進程
        if ( (pid = fork()) < 0 ) {   // 復制1個線程
            // 毛病處理
            cannot_execute(client);
            return;
        }
        sprintf(buf, "HTTP/1.0 200 OK\r\n");
        send(client, buf, strlen(buf), 0);
        // 子進程要調用CGI腳本
        if (pid == 0)  /* child: CGI script */
        {
            // 環境變量緩沖區,會存在溢出風險
            char meth_env[255];
            char query_env[255];
            char length_env[255];
            // 重定向管道
            // 把父進程讀寫管道的描寫符分別綁定到子進程的標準輸入和輸出
            // dup2功能與freopen()函數類似
            dup2(cgi_output[1], STDOUT);   // 把STDOUT重定向到cgi_output的寫入端
            dup2(cgi_input[0], STDIN);     // 把STDIN重定向到cgi_input的讀取端
            // 關閉沒必要要的描寫符
            close(cgi_output[0]);          // 關閉cgi_inout的寫入端和cgi_output的讀取端
            close(cgi_input[1]);
    
            // 服務器設置環境變量,即request_method的環境變量
            // 設置基本的CGI環境變量,要求類型、參數、長度之類
            sprintf(meth_env, "REQUEST_METHOD=%s", method);
            putenv(meth_env);
            if (strcasecmp(method, "GET") == 0) {
                // 設置query_string的環境變量
                sprintf(query_env, "QUERY_STRING=%s", query_string);
                putenv(query_env);
            }
            else {   /* POST */
                // 設置content_length的環境變量
                sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
                putenv(length_env);
            }
    
            // 用execl運行cgi程序
            execl(path, NULL);
            exit(0);
        } else {    /* parent */
    
            // 父進程代碼
            // 關閉cgi_input的讀取端和cgi_output的寫入端
            close(cgi_output[1]);
            close(cgi_input[0]);
            // 對Post要求,要直接write()給子進程
            // 這模樣進程所調用的腳本就能夠從標準輸入獲得Post數據
            if (strcasecmp(method, "POST") == 0)
                // 接收POST過來的數據
                for (i = 0; i < content_length; i++) {
                    recv(client, &c, 1, 0);
                    // 把POST數據寫入cgi_input,現在重定向到STDIN
                    write(cgi_input[1], &c, 1);
                }
            // 然后父進程再從輸出管道里面讀出所有結果,返回給客戶端
            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);
        }
    }
    
  • 參考文獻

    http://armsword.com/2014/10/29/tinyhttpd-code-analyse/

    http://blog.csdn.net/jcjc918/article/details/42129311

    http://techlog.cn/article/list/10182680

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 欧美一区二区三区久久 | 久久久久免费 | 久久久久av| 精品久久国产 | 欧美日韩另类在线 | 欧美日韩一区二区三区在线视频 | 欧美在线播放 | 亚洲视频在线播放 | 一区二区三区中文 | 日韩成人免费观看 | 精品视频一区二区三区 | 日韩视频一区二区 | 国产一级免费 | 97视频在线播放 | 国产a级全部精品 | 亚洲综合精品 | 久久精品国产v日韩v亚洲 | 中文字幕日韩视频 | 美女又黄又免费的视频 | 久久久久久久久国产精品 | 高清视频黄色 | 国产福利免费视频 | 成人在线高清 | 成人做爰视频www网站小优视频 | 免费视频一区二区 | 一区二区不卡视频 | 国产午夜在线 | 欧美激情xxxxx | 黄色一级大片在线观看 | 国产一区二区在线看 | 中文有码在线视频 | 国产区视频在线观看 | 国产精品精品视频 | 亚洲久久网 | 亚洲成人天堂 | 在线看日韩av | 久久国产成人精品 | 亚洲在线一区二区三区 | 国产一区二区精品 | 中文字幕日韩在线 | 三级毛片在线 |