多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > php開(kāi)源 > php教程 > Tinyhttp源碼分析

Tinyhttp源碼分析

來(lái)源:程序員人生   發(fā)布時(shí)間:2016-06-08 08:38:05 閱讀次數(shù):3032次

Tinyhttp源碼分析

  • 簡(jiǎn)介

    Tinyhttp是1個(gè)輕量型Http Server,使用C語(yǔ)言開(kāi)發(fā),全部代碼只500多行,還包括1個(gè)簡(jiǎn)單Client。

  • 源碼剖析

    Tinyhttp程序的邏輯為:1個(gè)無(wú)線(xiàn)循環(huán),1個(gè)要求,創(chuàng)建1個(gè)線(xiàn)程,以后線(xiàn)程函數(shù)處理每一個(gè)要求,然后解析HTTP要求,做1些判斷,以后判斷文件是不是可履行,不可履行,打開(kāi)文件,輸出給客戶(hù)端(閱讀器),可履行就創(chuàng)建管道,父子進(jìn)程進(jìn)行通訊。其整體處理流程以下:

    每一個(gè)函數(shù)的作用以下:

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

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

  • 核心函數(shù)

    1)main()函數(shù)

    // 服務(wù)器main函數(shù)
    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個(gè)監(jiān)聽(tīng)套接字,在對(duì)應(yīng)的端口建立httpd服務(wù)
        server_sock = startup(&port);
        printf("httpd running on port %d\n", port);
        // 進(jìn)入循環(huán),服務(wù)器通過(guò)調(diào)用accept等待客戶(hù)真?zhèn)€連接,Accept會(huì)以阻塞的方式運(yùn)行,直到
        // 有客戶(hù)端連接才會(huì)返回。連接成功后,服務(wù)器啟動(dòng)1個(gè)新的線(xiàn)程來(lái)處理客戶(hù)真?zhèn)€要求,處理
        // 完成后,重新等待新的客戶(hù)端要求。
        while (1)
        {
            // 返回1個(gè)已連接套接字,套接字收到客戶(hù)端連接要求
            client_sock = accept(server_sock,
                    (struct sockaddr *)&client_name,
                    &client_name_len);
            if (client_sock == ⑴)
                error_die("accept");
            // 派生線(xiàn)程用accept_request函數(shù)處理新要求。
            /* accept_request(client_sock); */
            if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock) != 0)
                perror("pthread_create");
        }
        // 出現(xiàn)意外退出的時(shí)候,關(guān)閉socket
        close(server_sock);
    
        return(0);
    }
    

    2)startup()函數(shù)

    // startup函數(shù):依照TCP連接的正常流程順次調(diào)用socket,bind,listen函數(shù)。
    // 監(jiān)聽(tīng)套接字端口既可以指定也能夠動(dòng)態(tài)分配1個(gè)隨機(jī)端口
    int startup(u_short *port)
    {
        int httpd = 0;
        struct sockaddr_in name;
        // 創(chuàng)建1個(gè)socket,建立socket連接
        httpd = socket(PF_INET, SOCK_STREAM, 0);
        if (httpd == ⑴)
            error_die("socket");
        // 填充結(jié)構(gòu)體
        memset(&name, 0, sizeof(name));
        name.sin_family = AF_INET;
        name.sin_port = htons(*port);
        name.sin_addr.s_addr = htonl(INADDR_ANY);
        // 將socket綁定到對(duì)應(yīng)的端口上
        if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
            error_die("bind");
        // 如果當(dāng)前指定的端口是0,則動(dòng)態(tài)隨機(jī)分配1個(gè)端口
        if (*port == 0)  /* if dynamically allocating a port */
        {
            socklen_t namelen = sizeof(name);
            // 1.getsockname()可以取得1個(gè)與socket相干的地址
            //  1)服務(wù)器端可以通過(guò)它得到相干客戶(hù)端地址
            //  2)客戶(hù)端可以得到當(dāng)前已連接成功的socket的IP和端口
            // 2.在客戶(hù)端不進(jìn)行bind而直接連接服務(wù)器時(shí),且客戶(hù)端需要知道當(dāng)前使用哪一個(gè)IP地址
            //   進(jìn)行通訊時(shí)比較有用(如多網(wǎng)卡的情況)
            if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == ⑴)
                error_die("getsockname");
            *port = ntohs(name.sin_port);
        }
        // 開(kāi)始監(jiān)聽(tīng)
        if (listen(httpd, 5) < 0)
            error_die("listen");
        // 返回socket id
        return(httpd);
    }
    

    3)accept_request()函數(shù)

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

    4)execute_cgi()函數(shù)

    此函數(shù)履行流程以下:

    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 = ⑴;
    
        // 首先需要根據(jù)要求是Get還是Post,來(lái)分別進(jìn)行處理
        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,那末就需要讀出要求長(zhǎng)度即Content-Length
        else if (strcasecmp(method, "POST") == 0) /*POST*/
        {
            // 對(duì)POST的HTTP要求中找出content_length
            numchars = get_line(client, buf, sizeof(buf));
            while ((numchars > 0) && strcmp("\n", buf))
            {
                // 使用\0進(jìn)行分割
                buf[15] = '\0';
                // HTTP要求的特點(diǎn)
                if (strcasecmp(buf, "Content-Length:") == 0)
                    content_length = atoi(&(buf[16]));
                numchars = get_line(client, buf, sizeof(buf));
            }
            // 如果要求長(zhǎng)度不合法(比如根本就不是數(shù)字),那末就報(bào)錯(cuò),即沒(méi)有找到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本身,生成兩個(gè)進(jìn)程
        if ( (pid = fork()) < 0 ) {   // 復(fù)制1個(gè)線(xiàn)程
            // 毛病處理
            cannot_execute(client);
            return;
        }
        sprintf(buf, "HTTP/1.0 200 OK\r\n");
        send(client, buf, strlen(buf), 0);
        // 子進(jìn)程要調(diào)用CGI腳本
        if (pid == 0)  /* child: CGI script */
        {
            // 環(huán)境變量緩沖區(qū),會(huì)存在溢出風(fēng)險(xiǎn)
            char meth_env[255];
            char query_env[255];
            char length_env[255];
            // 重定向管道
            // 把父進(jìn)程讀寫(xiě)管道的描寫(xiě)符分別綁定到子進(jìn)程的標(biāo)準(zhǔn)輸入和輸出
            // dup2功能與freopen()函數(shù)類(lèi)似
            dup2(cgi_output[1], STDOUT);   // 把STDOUT重定向到cgi_output的寫(xiě)入端
            dup2(cgi_input[0], STDIN);     // 把STDIN重定向到cgi_input的讀取端
            // 關(guān)閉沒(méi)必要要的描寫(xiě)符
            close(cgi_output[0]);          // 關(guān)閉cgi_inout的寫(xiě)入端和cgi_output的讀取端
            close(cgi_input[1]);
    
            // 服務(wù)器設(shè)置環(huán)境變量,即request_method的環(huán)境變量
            // 設(shè)置基本的CGI環(huán)境變量,要求類(lèi)型、參數(shù)、長(zhǎng)度之類(lèi)
            sprintf(meth_env, "REQUEST_METHOD=%s", method);
            putenv(meth_env);
            if (strcasecmp(method, "GET") == 0) {
                // 設(shè)置query_string的環(huán)境變量
                sprintf(query_env, "QUERY_STRING=%s", query_string);
                putenv(query_env);
            }
            else {   /* POST */
                // 設(shè)置content_length的環(huán)境變量
                sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
                putenv(length_env);
            }
    
            // 用execl運(yùn)行cgi程序
            execl(path, NULL);
            exit(0);
        } else {    /* parent */
    
            // 父進(jìn)程代碼
            // 關(guān)閉cgi_input的讀取端和cgi_output的寫(xiě)入端
            close(cgi_output[1]);
            close(cgi_input[0]);
            // 對(duì)Post要求,要直接write()給子進(jìn)程
            // 這模樣進(jìn)程所調(diào)用的腳本就能夠從標(biāo)準(zhǔn)輸入獲得Post數(shù)據(jù)
            if (strcasecmp(method, "POST") == 0)
                // 接收POST過(guò)來(lái)的數(shù)據(jù)
                for (i = 0; i < content_length; i++) {
                    recv(client, &c, 1, 0);
                    // 把POST數(shù)據(jù)寫(xiě)入cgi_input,現(xiàn)在重定向到STDIN
                    write(cgi_input[1], &c, 1);
                }
            // 然后父進(jìn)程再?gòu)妮敵龉艿览锩孀x出所有結(jié)果,返回給客戶(hù)端
            while (read(cgi_output[0], &c, 1) > 0)
                send(client, &c, 1, 0);
    
            // 關(guān)閉管道
            close(cgi_output[0]);
            close(cgi_input[1]);
            // 最后等待子進(jìn)程結(jié)束,即等待子進(jìn)程
            waitpid(pid, &status, 0);
        }
    }
    
  • 參考文獻(xiàn)

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

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

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

生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線(xiàn)----------------------------
分享到:
------分隔線(xiàn)----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 日本高清中文字幕一区二区三区a | 网址在线 | 免费网站在线观看国产v片 免费网站在线看 | 亚洲另类小说图片 | www一片黄| 亚洲经典自拍 | 一本久道热中字伊人 | 国产精品三级一区二区 | 97就去干| 五月激情婷婷综合 | 日韩大片免费看 | 九色精品在线 | 狠狠色综合网 | 亚洲在线免费免费观看视频 | 国产精彩视频 | 性做久久久久久蜜桃花 | 亚洲欧美另类色妞网站 | free asian xxxxx黑人| 泰国一级毛片aaa下面毛多 | 欧美一级毛片欧美一级成人毛片 | 亚洲精品成人图区 | wwwxx欧美| 亚洲伊人影院 | 久久国产精品自由自在 | 中文国产成人精品少久久 | jizz日本zzz老师水多视频 | 欧美一区二区三区视频在线 | 日产国产欧美视频一区精品 | 在线日本中文字幕 | 日韩欧美天堂 | 日本高清免费网站zzzzzzzz | 武则天a级在线观看 | 夜夜躁日日躁狠狠 | 国产乱码精品一区二区三区中 | 爽一爽色视频 | 精品乱码一区二区三区在线 | 欧美黑人巨大性极品hd | 欧美日韩在线观看视频 | xxx性欧美| 99久久精品一区二区三区 | 精品国产三级v |