tinyhttp源码阅读

1.综述

这是一个小型的开源http服务,总代码量加上注释一共482行,实际上真正的核心代码就350行左右。虽然代码量不多,但是一款http服务器最基本的通信业务流程都具备了,当然这个服务器支持的方法也非常的少量,只支持GET和POST两种方法。处理流程就是程序起来就一直处于监听状态,当有客户端连接的时候就启动一个新的线程进行服务器,软件处理采用的是同步阻塞的方式进行处理的。如果客户端请求的是静态文件的话就直接读取本地文件进行源码发送,发送过程没有加密等其他冗余操作,做起来也是非常的干脆利索,如果需要调用本地cgi进行处理的话则采用匿名管道的方式进行通信,将客户端需求通过匿名管道传达给子进程,然后在父进程里面进行等待,直到子进程执行完毕,通过匿名管道将执行结果返回之后父进程将内容直接发送给客户端,之后父进程再退出。

代码结构如图所示:


真正的服务器代码都在httpd.c这个文件里面,其他的simpleclient.c这是一个测试使用的客户端代码,试试发送一个字符再接收一个字符,没有什么用处,在htdocs里面全部是http测试使用的静态网页代码以及脚本代码。所以要学习明白这个开源项目的代码也就是直接吧httpd.c文件里面的代码了解清楚即可,其他的辅助测试代码则可以根据个人兴趣来说。

函数列表:

#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
//处理从套接字上监听到的一个HTTP请求
void accept_request(int);
//返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST
void bad_request(int);
//读取服务器上某个文件写到socket套接字,请求的静态文件
void cat(int, FILE *);
//处理发生在执行cgi程序时出现的错误
void cannot_execute(int);
//把错误信息写到perror并退出
void error_die(const char *);
//运行cgi程序的处理,是主要的函数
void execute_cgi(int, const char *, const char *, const char *);
//读取套接字的一行,把回车换行等情况都统一为换行符结束
int get_line(int, char *, int);
//把HTTP响应的头部写到套接字
void headers(int, const char *);
//处理找不到请求的文件时的情况
void not_found(int);
//调用cat函数把服务器文件返回给浏览器
void serve_file(int, const char *);
//初始化httpd服务,包括建立套接字,绑定端口,进行监听等
int startup(u_short *);
//返回给浏览器表明收到的HTTP请求所用的method不被支持
void unimplemented(int);


2.流程

处理流程如下:

(1) 服务器启动,在指定端口或随机选取端口绑定 httpd 服务。

     (2)收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数。

     (3)取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ? 后面的 GET 参数。

     (4) 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页。

     (5)如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本。

    (6)读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200  状态码写到套接字。

    (7) 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程。

    (8) 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序。

    (9) 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。这一部分比较乱,见下图说明:


                                                     

                                                                                                         图 1    管道初始状态


                                                                                            

                                                                                                        图 2  管道最终状态 


    (10) 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。




3.源码

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>

#define ISspace(x) isspace((int)(x))

#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
//处理从套接字上监听到的一个HTTP请求
void accept_request(int);
//返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST
void bad_request(int);
//读取服务器上某个文件写到socket套接字,请求的静态文件
void cat(int, FILE *);
//处理发生在执行cgi程序时出现的错误
void cannot_execute(int);
//把错误信息写到perror并退出
void error_die(const char *);
//运行cgi程序的处理,是主要的函数
void execute_cgi(int, const char *, const char *, const char *);
//读取套接字的一行,把回车换行等情况都统一为换行符结束
int get_line(int, char *, int);
//把HTTP响应的头部写到套接字
void headers(int, const char *);
//处理找不到请求的文件时的情况
void not_found(int);
//调用cat函数把服务器文件返回给浏览器
void serve_file(int, const char *);
//初始化httpd服务,包括建立套接字,绑定端口,进行监听等
int startup(u_short *);
//返回给浏览器表明收到的HTTP请求所用的method不被支持
void unimplemented(int);

/**********************************************************************/
/* 处理从套接字上监听到的一个HTTP请求,根据不同的请求方式,将处理路由到不同的分支
 * 参数: client-socket句柄
/**********************************************************************/
void accept_request(int client)
{
    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;
    char *query_string = NULL;

    /*得到请求的第一行*/
    // 一个HTTP请求报文由请求行(requestline)、请求头部(header)、空行和请求数据4个部分
    // 组成,请求行由请求方法字段(get或post)、URL字段和HTTP协议版本字段3个字段组成,它们
    // 用空格分隔。如:GET /index.html HTTP/1.1。
    // 解析请求行,把方法字段保存在method变量中。
    // 读取HTTP头第一行:GET/index.php HTTP 1.1
    numchars = get_line(client, buf, sizeof(buf));
    i = 0; 
    j = 0;
    //把客户端的请求方法存到 method 数组
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
    {
        method[i] = buf[j];
        i++; 
        j++;
    }
    method[i] = '\0';

    //如果既不是GET又不是POST则无法处理
    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        unimplemented(client);
        return;
    }
    //POST 的时候开启cgi
    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    //读取 url 地址,第一个while是为了跳过一个空格,方便从url的实际开始字符串读取
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        /*存下 url */
        url[i] = buf[j];
        i++; 
        j++;
    }
    url[i] = '\0';

    // 先处理如果是GET请求的情况
    // 如果是get方法,请求参数和对应的值附加在URL后面,利用一个问号(“?”)代表URL的结
    // 尾与请求参数的开始,传递参数长度受限制。如index.jsp?10023,其中10023就是要传递
    // 的参数。这段代码将参数保存在query_string中。
    if (strcasecmp(method, "GET") == 0)
    {
        /* 待处理请求为 url */
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        /* GET 方法特点,? 后面为参数*/ 
        if (*query_string == '?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    //格式化url到path数组,html文件都在htdocs中 
    sprintf(path, "htdocs%s", url);  
    /*默认情况为 index.html */  
    if (path[strlen(path) - 1] == '/')  
        strcat(path, "index.html");  
    /*根据路径找到对应文件 */  
    if (stat(path, &st) == -1) {  
        /*把所有 headers 的信息都丢弃*/  
        while ((numchars > 0) && strcmp("\n", buf)) 
            numchars = get_line(client, buf, sizeof(buf));  
        /*回应客户端找不到*/  
        not_found(client);  
    }
    else
    {
        /*如果是个目录,则默认使用该目录下 index.html 文件*/  
        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;  
        /*不是 cgi,直接把服务器文件返回,否则执行 cgi */  
        if (!cgi)  
            serve_file(client, path);  
        else  
            execute_cgi(client, path, method, query_string);  
    }
    /*断开与客户端的连接(HTTP 特点:无连接)*/
    close(client);
}


/**********************************************************************/
/* 读取服务器上某个文件写到socket套接字,请求的静态文件
 * 参数: client-socket句柄
 *       resource-文件描述符
/**********************************************************************/
void cat(int client, FILE *resource)
{
    char buf[1024];
    /*读取文件中的所有数据写到 socket */
    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

/**********************************************************************
 * 把错误信息写到perror并退出
 *
 **********************************************************************/
void error_die(const char *sc)
{
    /*出错信息处理 */
    perror(sc);
    exit(1);
}

/**********************************************************************/
/* 运行cgi程序的处理,是主要的函数,需要设置环境变量,
 * 执行cgi程序使用的是多进程方式,通信则采用的是匿名管道。
 * 参数: client-socket通信描述符
 *       path-cgi路径
         method-请求方法
         query_string-参数
/**********************************************************************/
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)  
        /*把所有的 HTTP header 读取并丢弃*/  
        while ((numchars > 0) && strcmp("\n", buf))
            numchars = get_line(client, buf, sizeof(buf));  
    else/* 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 == -1) {  
            /*错误请求*/  
            bad_request(client);  
            return;  
        }  
    }

    /* 正确,HTTP 状态码 200 */  
    sprintf(buf, "HTTP/1.0 200 OK\r\n");  
    send(client, buf, strlen(buf), 0);  
  
    /* 建立管道*/  
    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;  
    }  
    if (pid == 0)  /* child: CGI script */  
    {  
        char meth_env[255];  
        char query_env[255];  
        char length_env[255];  
  
        /* 把 STDOUT 重定向到 cgi_output 的写入端 */  
        dup2(cgi_output[1], 1);  
        /* 把 STDIN 重定向到 cgi_input 的读取端 */  
        dup2(cgi_input[0], 0);  
        /* 关闭 cgi_input 的写入端 和 cgi_output 的读取端 */  
        close(cgi_output[0]);  
        close(cgi_input[1]);  
        /*设置 request_method 的环境变量*/  
        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, path, NULL);  
        exit(0);  
    }
    else/* parent */ 
    {      
        /* 关闭 cgi_input 的读取端 和 cgi_output 的写入端 */  
        close(cgi_output[1]);  
        close(cgi_input[0]);  
        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);  
            } 
        }
        /*读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT */  
        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);  
    }
}

/**********************************************************************/
/* 读取套接字的一行,把回车换行等情况都统一为换行符结束
 * 参数: client-socket通信描述符
 *       buf-数据缓冲区
         size-接收缓冲区大小
 * 返回值: 实际接收数据的大小
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{  
    int i = 0;  
    char c = '\0';  
    int n;  
  
    /*把终止条件统一为 \n 换行符,标准化 buf 数组*/  
    while ((i < size - 1) && (c != '\n'))  
    {  
        /*一次仅接收一个字节*/  
        n = recv(sock, &c, 1, 0);  
        /* DEBUG printf("%02X\n", c); */  
        if (n > 0)  
        {  
            /*收到 \r 则继续接收下个字节,因为换行符可能是 \r\n */  
            if (c == '\r')  
            {  
                /*使用 MSG_PEEK 标志使下一次读取依然可以得到这次读取的内容,可认为接收窗口不滑动*/  
                n = recv(sock, &c, 1, MSG_PEEK);  
                /* DEBUG printf("%02X\n", c); */  
                /*但如果是换行符则把它吸收掉*/  
                if ((n > 0) && (c == '\n'))  
                    recv(sock, &c, 1, 0);  
                else  
                    c = '\n';  
            }  
            /*存到缓冲区*/  
            buf[i] = c;  
            i++;  
        }  
        else  
            c = '\n';  
    }  
    buf[i] = '\0';  
  
    /*返回 buf 数组大小*/  
    return(i);  
}


/**********************************************************************/
/* 把HTTP响应的头部写到套接字
/* 参数: client-socket通信描述符
 *       filename-请求文件名称
/**********************************************************************/
void headers(int client, const char *filename)
{
    char buf[1024];  
    (void)filename;  /* could use filename to determine file type */  
  
    /*正常的 HTTP header */  
    strcpy(buf, "HTTP/1.0 200 OK\r\n");  
    send(client, buf, strlen(buf), 0);  
    /*服务器信息*/  
    strcpy(buf, SERVER_STRING);  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "Content-Type: text/html\r\n");  
    send(client, buf, strlen(buf), 0);  
    strcpy(buf, "\r\n");  
    send(client, buf, strlen(buf), 0);
}


/**********************************************************************/
/* 调用cat函数把服务器文件返回给浏览器
 * 参数: client-socket通信描述符
 *       filename-请求文件名称
/**********************************************************************/
void serve_file(int client, const char *filename)
{
    FILE *resource = NULL;  
    int numchars = 1;  
    char buf[1024];  
  
    /*读取并丢弃 header */  
    buf[0] = 'A'; 
    buf[1] = '\0';  
    while ((numchars > 0) && strcmp("\n", buf)) 
        numchars = get_line(client, buf, sizeof(buf));  
  
    /*打开 sever 的文件*/  
    resource = fopen(filename, "r");  
    if (resource == NULL)  
        not_found(client);  
    else  
    {  
        /*写 HTTP header */  
        headers(client, filename);  
        /*复制文件*/  
        cat(client, resource);  
    }  
    fclose(resource);
}

/**********************************************************************/
/* 初始化httpd服务,包括建立套接字,绑定端口,进行监听等
 * 依次调用socket,bind,listen函数
 * 参数: 端口
 * 返回值: socket 句柄
/**********************************************************************/
int startup(u_short *port)
{
    int httpd = 0;  
    struct sockaddr_in name;  
  
    /*建立 socket */  
    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); 
    //将socket绑定到对应的端口上
    if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)  
        error_die("bind");  
    //如果当前指定端口是 0,则动态随机分配一个端口  
    if (*port == 0)
    {  
        int namelen = sizeof(name);
        // 1.getsockname()可以获得一个与socket相关的地址
        //  1)服务器端可以通过它得到相关客户端地址
        //  2)客户端可以得到当前已连接成功的socket的IP和端口
        // 2.在客户端不进行bind而直接连接服务器时,且客户端需要知道当前使用哪个IP地址
        //   进行通信时比较有用(如多网卡的情况)
        if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)  
            error_die("getsockname");  
        *port = ntohs(name.sin_port);  
    }  
    //开始监听 
    if (listen(httpd, 5) < 0)  
        error_die("listen");  
    //返回 socket id   
    return(httpd);
}

// 服务器main函数
int main(void)
{
    int server_sock = -1;  
    u_short port = 0;  
    int client_sock = -1;  
    struct sockaddr_in client_name;  
    int client_name_len = sizeof(client_name);  
    pthread_t newthread;  
  
    //在对应端口建立 httpd 服务 
    server_sock = startup(&port);  
    printf("httpd running on port %d\n", port);  
    // 进入循环,服务器通过调用accept等待客户端的连接,Accept会以阻塞的方式运行,直到
    // 有客户端连接才会返回。连接成功后,服务器启动一个新的线程来处理客户端的请求,处理
    // 完成后,重新等待新的客户端请求。
    while (1)  
    {  
        //套接字收到客户端连接请求 
        client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);  
        if (client_sock == -1)  
            error_die("accept");  
        /*派生新线程用 accept_request 函数处理新请求*/  
        if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)  
            perror("pthread_create");  
    }  
    //出现意外退出的时候,关闭socket
    close(server_sock);  
  
    return(0);
}

/**********************************************************************/
/* 返回给浏览器表明收到的HTTP请求所用的method不被支持
 * 参数: client-socket句柄
/**********************************************************************/
void unimplemented(int client)
{
    char buf[1024];  
  
    /* HTTP method 不被支持*/  
    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");  
    send(client, buf, strlen(buf), 0);  
    /*服务器信息*/  
    sprintf(buf, SERVER_STRING);  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "Content-Type: text/html\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</TITLE></HEAD>\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</BODY></HTML>\r\n");  
    send(client, buf, strlen(buf), 0);
}


/**********************************************************************/
/* 处理找不到请求的文件时的情况,给客户端发送404的错误信息
 * 参数: client-socket通信描述符
/**********************************************************************/
void not_found(int client)
{
    char buf[1024];

    /* 404 页面 */  
    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");  
    send(client, buf, strlen(buf), 0);  
    /*服务器信息*/  
    sprintf(buf, SERVER_STRING);  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "Content-Type: text/html\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "your request because the resource specified\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "is unavailable or nonexistent.\r\n");  
    send(client, buf, strlen(buf), 0);  
    sprintf(buf, "</BODY></HTML>\r\n");  
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* 处理发生在执行cgi程序时出现的错误
 * 参数: client-socket句柄
/**********************************************************************/
void cannot_execute(int client)
{
    char buf[1024];
    /* 回应客户端 cgi 无法执行*/
    sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
    send(client, buf, strlen(buf), 0);
}

/**********************************************************************/
/* 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST
 * 参数: client-socket句柄
/**********************************************************************/
void bad_request(int client)
{
    char buf[1024];
    /*回应客户端错误的 HTTP 请求 */ 
    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "<P>Your browser sent a bad request, ");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "such as a POST without a Content-Length.\r\n");
    send(client, buf, sizeof(buf), 0);
}

4.编译

编译的时候Maklefile写的不太正确,编译会遇到一个错误,错误信息如下:

/usr/bin/ld: cannot find -lsocket
collect2: error: ld returned 1 exit status
make: *** [httpd] Error 1
这里修改一下Maklefile文件即可,将Maklefile文件中的- lsocket去掉,没有必要这么写:

all: httpd

httpd: httpd.c
        gcc -W -Wall -lpthread -o httpd httpd.c

clean:
        rm httpd

5.其他

这个服务器,学习http服务开发基础流程非常不错,但是真正的项目上使用有很大的风险,比如线程不受限制,有多少请求就会开启多少线程,这个事很危险的。其次运行无日志,没法维护,当然真正的项目上也不会使用这个,仅仅用于学习使用是非常不错的一个web雏形。

6.其他知识点

int fstat(int fdp, struct stat *struct_stat);  //通过文件描述符获取文件对应的属性。fdp为文件描述符
下面是这个结构的结构
struct stat {
        mode_t     st_mode;       //文件对应的模式,文件,目录等
        ino_t      st_ino;       //inode节点号
        dev_t      st_dev;        //设备号码
        dev_t      st_rdev;       //特殊设备号码
        nlink_t    st_nlink;      //文件的连接数
        uid_t      st_uid;        //文件所有者
        gid_t      st_gid;        //文件所有者对应的组
        off_t      st_size;       //普通文件,对应的文件字节数
        time_t     st_atime;      //文件最后被访问的时间
        time_t     st_mtime;      //文件内容最后被修改的时间
        time_t     st_ctime;      //文件状态改变时间
        blksize_t st_blksize;    //文件内容对应的块大小
        blkcnt_t   st_blocks;     //伟建内容对应的块数量
      };
stat结构体中的st_mode 则定义了下列数种情况:
    S_IFMT   0170000    文件类型的位遮罩
    S_IFSOCK 0140000    scoket
    S_IFLNK 0120000     符号连接
    S_IFREG 0100000     一般文件
    S_IFBLK 0060000     区块装置
    S_IFDIR 0040000     目录
    S_IFCHR 0020000     字符装置
    S_IFIFO 0010000     先进先出
    S_ISUID 04000     文件的(set user-id on execution)位
    S_ISGID 02000     文件的(set group-id on execution)位
    S_ISVTX 01000     文件的sticky位
    S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限
    S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
    S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限
    S_IRGRP 00040             用户组具可读取权限
    S_IWGRP 00020             用户组具可写入权限
    S_IXGRP 00010             用户组具可执行权限
    S_IROTH 00004             其他用户具可读取权限
    S_IWOTH 00002             其他用户具可写入权限
    S_IXOTH 00001             其他用户具可执行权限
    上述的文件类型在POSIX中定义了检查这些类型的宏定义:
    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (s3e)        是否为先进先出
    S_ISSOCK (st_mode)   是否为socket
若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录。

参考博客:
http://blog.csdn.net/yzhang6_10/article/details/51534409
http://blog.csdn.net/jcjc918/article/details/42129311




猜你喜欢

转载自blog.csdn.net/kongshuai19900505/article/details/79270821
今日推荐