下面来看看boa.c吧
/* 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"); } 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"); }
这里设定了权限掩码为~600,也就是生成文件权限为rw——-,然后打开/dev/null,它相当于一个黑洞,输入任何东西都会丢失,读取也什么都读取不到,因为服务器进程是在守护进程,所以将标准输入输出定向到/dev/null。下面看getopt:
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); } }
getopt对程序的参数进行处理,boa的很简单,选项只有三个。getopt有三个参数,前两个参数就是main函数的参数,第三个参数是一个字符串,每个字符代表一个选项,如果选项后面有冒号,说明这个选项后面必须要跟参数,当然,实际调用程序的时候,参数的顺序可能会不一样。如果选项有一个关联值,则外部变量optarg会指向这个值,为了防止optarg被重新赋值,server_root = strdup(optarg) 把字符串复制到server_root。选项处理完毕的时候getopt会返回-1.
getopt还有相关变量 optind,opterr,optopt。
如果遇到一个无法识别的选项,getopt返回一个问号(?),并把它保存到外部变量optopt中
如果选项要求关联值,但是没有提供,那么返回一个问号。
optind被设置为下一个待处理单数的索引,getopt用他来记录自己的进度。getopt重写了argv数组,所有非选项参数集中在一起,从argv[optind]开始。
perror()用来将上一个函数发生错误的原因输出到标准错误(stderr)。参数所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
整个部分的作用是,-c指定服务器目录,-r指定chroot目录(暂时不知道为啥这样做),-d指明不以守护进程方式运行?
fixup_server_root();//修正server_root read_config_files();//读取配置文件 open_logs();//打开日志文件 server_s = create_server_socket();//创建套接字 init_signals();//初始化信号。。 drop_privs();//更改权限 create_common_env();//环境啥的?环境变量? build_needs_escape();//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; }
getrlimit获取当前进程的资源限制。RLIMIT_NOFILE表示每个进程能打开的最大文件数。rlimit结构体中包含了两个变量,rlim_cur表示当前限制,rlim_max表示最大限制,硬限制。
/* 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; } }
调用fork,父进程退出,如果父进程在shell中执行,那么会被认为执行完毕。
到底为止,创建守护进程的几个规则都已经完成(APUE page 342),除了那个setsid,setsid创建一个新会话,成为新会话首进程,新进程组组长。
最后的select_loop(server_s)就开始接收链接什么的了吧。重点?
下一次将分析boa.c中后面声明的三个函数。
不知你弄明白read_config_files函数怎样读取配置文件没?