httpd服务器代码详解

httpd服务器代码详解

github链接:https://github.com/EZLippi/Tinyhttpd

1. main

返回一个新的 socket 描述符专门用于和这个客户端通信

client_sock = accept(server_sock,
	  (struct sockaddr *)&client_name,
	   &client_name_len);
 if (client_sock == -1)
     error_die("accept");

创建一个线程处理连接
accept_request表示要执行的函数
因为后续没有pthread_join进行线程回收,不需要记录newthread

if (pthread_create(&newthread, NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)
            perror("pthread_create");

2. startup

创建socket,地址清零和初始化,设置端口复用,绑定绑定地址和端口

    httpd = socket(PF_INET, SOCK_STREAM, 0);

    //如果初始化失败error_die
    //error_die封装了perror();exit(1);
    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);

    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");

3. accept_request

strcasecmp逐字节比较,忽略字符的大小写
如果 method 不是 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++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) {   
    url[i] = buf[j];
    i++; j++;
}

如果是 GET 请求,就去 URL 中查找是否含有 ?
如果找到了,就说明这个 GET 请求有参数

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) {            //stat() 用来检查文件是否存在
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            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;
        if (!cgi)
            serve_file(client, path);
        else
            execute_cgi(client, path, method, query_string);
    }

4. get_line

见注释

int get_line(int sock, char *buf, int size) {
    int i = 0;
    char c = '\0';
    int n;
    //每次最多读取 size - 1 个字符,一旦读取到换行符 \n 就停止
    while ((i < size - 1) && (c != '\n')) {
        //从 socket 读取 1 个字节放入 c,逐字节读取
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        //如果遇到回车符 \r,看看后面是不是换行 \n,
        //如果是,就一起吃掉,如果不是,就把 \r 当作换行                                       
        if (n > 0) {
            if (c == '\r') {
                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';
    return(i);
}

5. execute_cgi

  • 创建两个管道:cgi_input 和 cgi_output

  • 创建一个子进程(fork())

  • 子进程负责运行 CGI 程序,输入/输出通过管道通信

  • 父进程负责:

    • 给子进程输入参数(POST body)

    • 读取 CGI 输出(HTML 或 JSON)并发给客户端

创建管道

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;
}

重定向输入输出

dup2(cgi_output[1], STDOUT);
dup2(cgi_input[0], STDIN);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值