1,下面是完整的boa.c中的main函数代码

int main(int argc, char **argv)
{
    int c;                      /* command line arg */
    int server_s;                   /* boa socket */

    /* set umask to u+rw, u-x, go-rwx */
    c = umask(~0600);
    if (c == -1) {
        perror("umask");
        exit(1);
    }

    devnullfd = open("/dev/null", 0);
    
    /* make STDIN and STDOUT point to /dev/null */
    if (devnullfd == -1) {
        DIE("can't open /dev/null");
    }
    //【X】把文件标准输入和输出描述符定向为/dev/null
    if (dup2(devnullfd, STDIN_FILENO) == -1) {
        DIE("can't dup2 /dev/null to STDIN_FILENO");
    }

    if (dup2(devnullfd, STDOUT_FILENO) == -1) {
        DIE("can't dup2 /dev/null to STDOUT_FILENO");
    }
    //【X】当前时间搓
    /* but first, update timestamp, because log_error_time uses it */
    (void) time(&current_time);

    //【X】参数解析
    while ((c = getopt(argc, argv, "c:r:d")) != -1) {
        switch (c) {
        case 'c':
            if (server_root)
                free(server_root);
            server_root = strdup(optarg);
            if (!server_root) {
                perror("strdup (for server_root)");
                exit(1);
            }
            break;
        case 'r':
            if (chdir(optarg) == -1) {
                log_error_time();
                perror("chdir (to chroot)");
                exit(1);
            }
            if (chroot(optarg) == -1) {
                log_error_time();
                perror("chroot");
                exit(1);
            }
            if (chdir("/") == -1) {
                log_error_time();
                perror("chdir (after chroot)");
                exit(1);
            }
            break;
        case 'd':
            do_fork = 0;
            break;
        default:
            fprintf(stderr, "Usage: %s [-c serverroot] [-r chroot] [-d]\n", argv[0]);
            exit(1);
        }
    }

    fixup_server_root();
    read_config_files();
    open_logs();
    server_s = create_server_socket();
    init_signals();
    drop_privs();
    create_common_env();
    build_needs_escape();

    if (max_connections < 1) {
        struct rlimit rl;

        /* has not been set explicitly */
        c = getrlimit(RLIMIT_NOFILE, &rl);
        if (c < 0) {
            perror("getrlimit");
            exit(1);
        }
        max_connections = rl.rlim_cur;
    }

    /* background ourself */
    if (do_fork) {
        switch(fork()) {
        case -1:
            /* error */
            perror("fork");
            exit(1);
            break;
        case 0:
            /* child, success */
            break;
        default:
            /* parent, success */
            exit(0);
            break;
        }
    }

    /* main loop */
    timestamp();

    status.requests = 0;
    status.errors = 0;

    start_time = current_time;
    
//网络和http协议的select实现(最主要函数)
    select_loop(server_s);
    return 0;
}

2,重点函数分析和调用

fixup_server_root() :

        把server_root函数设置为绝对路径,调用chdir并把程序的搜索路径切到server_root

内部主要函数有:

        normalize_path :把一个相对路径转换为绝对路径 ,通过getcwd函数获取当前的绝对路径,在拼接配置的path,组合

read_config_files(void):

        读取配置文件的参数,设置到全家变量中的属性,   内部调用这个函数

         if (yyparse()) { //Yacc 词法分析,有boa_grammar.y语法配置,编译时生成代码
                fputs("Error parsing config files, exiting\n", stderr);
                exit(1);
         }

主要设置: 
 server_name(服务名)  tempdir(零时目录)   single_post_limit (post限制)
 document_root (文件根目录) error_log_name access_log_name  cgi_log_name dirmaker

 

open_logs();  //将标准输出从定向到设置的路径或网络,具体实现在open_gen_fd

init_signals(); 通过struct sigaction设置多个信号的处理函数

drop_privs();  降低当前程序的执行权限

主要逻辑:

        if (getuid() == 0) { //降低权限 0表示root,从root用户降低权限为配置的用户名和组

                if (setgid(server_gid) == -1) { 
                    DIE("setgid");
                }
                if (setuid(server_uid) == -1) {
                    DIE("setuid");
                }       

                // 必须先设置组Id,在设置用户Id,具体id值可以查看/etc/passwd

        /* test for failed-but-return-was-successful setuid
         * http://www.securityportal.com/list-archive/bugtraq/2000/Jun/0101.html
         */
                if (setuid(0) != -1) {  //这是一个内核bug,这里尝试再次设置为root判断是否成功
                    DIE("icky Linux kernel bug!");
                }

        }

        else {         设置配置的uid和gid

         server_gid = getgid();
        server_uid = getuid(); 

}

    create_common_env();  // 创建一些全家环境变量,如path,...存储在common_cgi_env中
    build_needs_escape(); 、、ai解析说是用于处理http需要的字符的预先编码,有点没看懂,附上源码:

void build_needs_escape(void)
{
    unsigned int a, b;
    const unsigned char special[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789"
        "-_.!~*'():@&=+$,/?";
    /* 21 Mar 2002 - jnelson - confirm with Apache 1.3.23 that '?'
     * is safe to leave unescaped.
     */
    unsigned short i, j;

    b = 1;
    for (a=0; b!=0; a++) b=b<<1;
    /* I found $a bit positions available in an unsigned long. */
    if (a < NEEDS_ESCAPE_WORD_LENGTH) {
        fprintf(stderr,
                "NEEDS_ESCAPE_SHIFT configuration error -- "\
                "%d should be <= log2(%d)\n",
                NEEDS_ESCAPE_SHIFT, a);
        exit(1);
    } else if (a >= 2*NEEDS_ESCAPE_WORD_LENGTH) {
        /* needs_escape_shift configuration suboptimal */
    } else {
        /* Ahh, just right! */;
    }
    memset(_needs_escape, ~0, sizeof(_needs_escape));
    for(i = 0; i < sizeof(special) - 1; ++i) {
        j=special[i];
        if (j>=NEEDS_ESCAPE_BITS) {
            /* warning: character $j will be needlessly escaped. */
        } else {
            _needs_escape[NEEDS_ESCAPE_INDEX(j)]&=~NEEDS_ESCAPE_MASK(j);
        }
    }
}

守护进程的标准写法:

使用fork函数,主进程直接退出,让孩子进程接着执行,不产生僵死进程

    /* background ourself */
    if (do_fork) {
        switch(fork()) {
        case -1:
            /* error */
            perror("fork");
            exit(1);
            break;
        case 0:
            /* child, success */
            break;
        default:
            /* parent, success */
            exit(0);
            break;
        }
    }

3,对使用到的一些系统函数解析

1,int dup2(int oldfd, int newfd);
实现:
    复制文件描述符,并允许指定新文件描述符的值为newfd
    如果newfd打开,则会先关闭,然后将其文件指针指向oldfd

2,int chdir(const char * path);
实现: 
    改变当前程序的工作路径为path

3,int gethostname(char *name, size_t len);
实现:
    获取当前主机的标准主机名称

4,struct hostent *gethostbyname(const char *name);
实现:域名解析函数
struct hostent {
               char  *h_name;            /* official name of host */
               char **h_aliases;         /* alias list */
               int    h_addrtype;        /* host address type */
               int    h_length;          /* length of address */
               char **h_addr_list;       /* list of addresses */
           }
           #define h_addr h_addr_list[0] /* for backward compatibility */
h_name:表示的是主机的规范名
h_aliases: 主机的别名
h_addrtype :IPV4 or IPv6
h_length :主机IP地址长度
h_addr_list :ip地址列表

5, 一个函数的使用说明
  /* set the close-on-exec to true */
 if (fcntl(STDERR_FILENO, F_SETFD, 1) == -1) { 
        //如果不设置 close-on-exec ,子进程会继承 stderr 的文件描述符,
        DIE("unable to fcntl the error log");
 }
当调用exec等函数执行cgi时,关闭stderror输出文件描述符,防止cgi输出到不必要的log文件中

4,总结

        boa是一个嵌入式的http服务器,源码可下载,boa-0.94.13,分析了main函数的部分实现,最主要的函数实现select_loop将接下来分解,

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐