BOA代码笔记4
main.c(完?)从上次继续 上次我们看到了这个地方:
[cpp]view plaincopyprint?if (max_connections < 1)
structrlimit 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; case0:
/*child, success */ break; default:
/*parent, success */ exit(0);
break; /*main loop */
timestamp(); status.requests= 0;
status.errors= 0; start_time= current_time;
select_loop(server_s); return0;
第一个if块确定最大连接数。如果未指定,使用getrlimit来获得。
getrlimit(RLIMIT_NOFILE, &rl);
specifiesa value
one greater than the maximum file descriptor number that can be opened by this process. 第二个 if 块,如果 do_fork 为 1 ,那么我们从子进程运行,父进程结束。 timestamp 就 是一段小程序:
[cpp] view plaincopyprint?void timestamp(void)
{ log_error_time(); fprintf(stderr, "boa: server version %s\n", SERVER_VERSION);
log_error_time();
fprintf(stderr,"boa: server built "
__DATE__ " at " __TIME__ ".\n"); log_error_time();
fprintf(stderr, "boa: starting server pid=%d, port %d\n", (int)
getpid(), server_port); }
程序之前好像已经将 stderr 重定向到自定义的错误处理文件 中
了。然后,清空 status 的两个域,记录一下 start_time 。 然后就
开始select_loop 了?select_loop()ps :博主没啥经验, 并未通读过
源码,这也是第一次看正经点儿的源代码。这篇
写完最后仍然云里雾里,较多猜测。如有错误欢迎指正。如果想知道代码背后的思想流程架构,那就得等博主看完全部代码再总结了(如果我有这能力和耐心的话……)。
ps2:如果由于博主没有全局把握,看到哪儿讲哪儿的方式引起你的极度不适,我表示非常抱歉。并强烈推荐你自己去看源码,也许效果拔群。 :)先粘一下select_loop的代码:
[cpp]view plaincopyprint?void select_loop(int server_s) {FD_ZERO(&block_read_fdset);
FD_ZERO(&block_write_fdset);
/*set server_s
and req_timeout */ req_timeout.tv_sec = (ka_timeout
ka_timeout : REQUEST_TIMEOUT);
req_timeout.tv_usec = 0l; /* reset timeout */ /*
preset max_fd */ max_fd = -1; while (1)
{ if (sighup_flag) sighup_run();
if (sigchld_flag) sigchld_run(); if
(sigalrm_flag) sigalrm_run(); if
(sigterm_flag) {
if(sigterm_flag == 1)
sigterm_stage1_run(server_s); if
(sigterm_flag == 2 && !request_ready && !request_block)
} } /* reset max_fd */ max_fd = -1; if (request_block)
{ sigterm_stage2_run();
/*move selected req's from request_block to request_ready */fdset_update();
/*any blocked req's move from request_ready to request_block */process_requests(server_s); if (!sigterm_flag &&
total_connections< (max_connections - 10))
{BOA_FD_SET(server_s, &block_read_fdset); /* server always set*/ }req_timeout.tv_sec = (request_ready ? 0 : (ka_timeout ?
ka_timeout: REQUEST_TIMEOUT)); req_timeout.tv_usec = 0l;/* reset timeout */
if(select(max_fd + 1, &block_read_fdset,
&block_write_fdset,NULL,
(request_ready|| request_block ? &req_timeout :
NULL)) == -1) { | /* what is the appropriate if (errno = | |
continue; | /* while(1) */ | |
elseif (errno != EBADF)
{ DIE("select"); | } |
if
(FD_ISSET(server_s,&block_read_fdset)) pending_requests = 1; }}
清空block_read_fdset和block_write_fdset,这两个看名字我猜是用在 select里,具体用来表示哪些fd的集合以后才知道。
req_timeout用作select的时间,#define
REQUEST_TIMEOUT60 max_fd 置为-1然后进入了while(1)循环。首先是检测这么几个 flag:sighup_flag,sigchld_flag ,sigalrm_flag,sigterm_flag。boa的信号处理回头看一下 boa对信号的处理策略(voidinit_signals() 中):boa处理10个信号,其中 SIGPIPE,SIGUSR1,SIGUSR2忽略掉。
SIGSEGV,SIGBUS,SIGTERM,SIGHUP,SIGINT,SIGCHLD,SIGALRM有自己的信号处理函数。对于段错误SIGSEGV,记录一下出错时间写到日志里,然后就abort了。毕
竟无法恢复。
对于SIGBUS,在另外两处视情况可能要好好的处理SIGBUS,之后讲到再说。默认情况下像SIGSEGV一样,也是记录一下, abort掉。
SIGBUS这个信号,印象中在 mmap后错误访问时会产
生,百度一下发现,在一些体系结构上,访问未对齐的地址会产生。
对于SIGINT,收到这个信号时,记录一下,正常退出。这个信号可以由 ctrl+c发送给foregroundprocess 产生。剩下了这四个在 while循环里处理的信号。
SIGHUP用来重新读取config_file。先清空fdset,清空
read_config_file里动态分配的内存,清空 request_free链表,然后调用 read_config_file。
对于SIGCHLD的处理是典型的子进程处理方式,UNP里有总
结,如下:
[cpp]view plaincopyprint?sigchld_flag = 0; while ((pid = waitpid(-1,&status, WNOHANG)) > 0)
if (verbose_cgi_logs) { log_error_time();
time(Ot_time);
fprintf(stderr,"reaping
child %d: status %d\n", (int) pid, status); } return;
SIGALRM 只用来将 mime_hashtable 和 passwd_hashtable 里的
数据写到日志文件里。
SIGTERM 两种处理方式
sigterm_stage1_run ,记录一下时间, 清空 block_read_set , 关掉 server_s ,意味着不再接受新的连接。然后设置 sigterm_flag = 2; 下一次由 sigterm_stage2_run 来处理。
sigterm_stage2_run,里完成正常结束的第二阶段:clear_common_env();dump_mime(); dump_passwd(); dump_alias(); free_requests(); 后exit(0)。
SIGTERM通过两个函数使程序适当的中断。fd_update()信号处
理部分结束。
之后到达这么一段:
if(request_block)
/*move selected req's from request_block to request_ready */
fdset_update();源代码里对函数fdset_update();的
说明如下:
/*
Name:fdset_update
*Description: iterate through the blocked requests, checking whether
*that file descriptor has been set by select. Updatethe fd_set to
*reflect current status.
*Here, we need to do some things:
*- keepalive timeouts simply close
*(this is special:: a keepalive timeout is a timeout where
keepaliveis active but nothing has been read yet)
*- regular timeouts close + error
-stuff in buffer and fd ready? writeit out
-fd ready for other actions? dothem
*/一句话总结:fdset_update将合适的request从block链表
里移动到ready链表里。
boa里边有三个请求链表
request *request_ready = NULL;
request *request_block = NULL;
request *request_free = NULL; 连接需要 reqeust 结构体时优先从 果为空,将 malloc 一个 reqeust
fdset_update 进行如下处理: 首
/*ready list head */
/*blocked list head */
/*free list head */ 新的
request_free中提取。如简
单的描述一下,
先, 获取 time_since 为距离上次成功操作经历的时间。 如 果请求出于 keepalive 中, time_since 已经大于 ka_timeout (配置文件里可以配置) ,而且还没有读取到任何东西,那 么 request 的 status 变为 DEAD 。
如果 time_since 大于 REQUEST_TIMEOUT (60 ),那么 status 变为 DEAD 。
如果缓冲区有数据,而且 status 小于 DEAD : 如果不在
block_write_fdset里,那么放到block_write_fdset里。如果 fd已经在block_write_fdset里,调用ready_request,将request从block队列里转移到ready队列里,同时清除block_write_fdset里
的标志
ready_request函数的功能是根据status,从fdset中清除对应 fd。
其他情况:
状态为WRITE,PIPE_WRITE,DONE的请求,如果没有那就放到 block_write_fdset里,如果已经在了就调 用 ready_request。
状态为BODY_WRITE,将request的post_data_fd做以上处理。post_data_fd注释为/*fd for post data tmpfile */ 应该是客户端POST方法时的临时文件,以后再详看。
状态为PIPE_READ,将request的data_fd做类似处理,不过检查的是 block_read_fdset。
状态为DEAD,直接调用ready_request。其他的,检查 fd是否在block_read_fdset,并作相应处理。这块儿目前看
代码只能知道这么做,但不明白作者背后的思想,模型。先慢慢
来,整体看完一遍后应该能更好
了解。process_requests()之后是process_requests(),按注释来看,功能与之前的 fdset_update()正好相反,将适合的 reqeust从ready链表移动到block链表。process_requests()注
释如下:
/*
*Name: process_requests
*Description: Iterates through the ready queue, passing each
request
*to the appropriate handler for processing. It monitors the
*return value from handler functions, all of which return -1
*to indicate a block, 0 on completion and 1 to remain on the
readylist for more procesing.
*/对于每一个readyqueue 里的请求遍历处理,返回值-1表示需
要进入blockqueue ;返回值0表示请求结束;返回值1表示还
要在readyqueue 里。
首先检查是否有pending_requests,如果有调用
get_request(server_s);,接受一个connection,加入ready_queue。
get_request(server_s);其实比较复杂,这里先不详细说了。大体
功能是,接受一个请求,并做一些简单的初始化,加入
ready_queue。然后开始pollready 链表:如果有数据要写,状态不是 DEAD或DONE,req_flush。之所以特殊处理,因为返
回值特殊 -2=error,-1=blocked, or bytes left 。然后对retval做规范处理。其他状态处理如下:
[cpp]view plaincopyprint?switch (current->status) { caseREAD_HEADER:
case ONE_CR: case TWO_CR: read_header(current); case BODY_READ: read_body(current); | case ONE_LF: break; |
case BODY_WRITE: write_body(current); case WRITE: process_get(current); case PIPE_READ: read_from_pipe(current); case PIPE_WRITE: write_from_pipe(current); case DONE: terminate the request */ req_flush(current); | retval = break; break; break; break; retval = /* |
*retval can be -2=error, -1=blocked, or bytes left
*/ | if (retval == 2) { /* error */ | retval |
current->status = DEAD; | ||
= 0; | } else if (retval > 0) | |
1;} { retval = break; case DEAD
retval = 0;
current->buffer_end= 0;
SQUASH_KA(current); break; default: retval = 0;
fprintf(stderr, "Unknown status (%d), " "closing!\n", current->status);
current->status= DEAD;
break;}
每个状态的处理函数可能有自己的返回值,个别的需要规范化处
理,以尽量满足:
返回值-1表示需要进入blockqueue ;返回值0表示请求结束;返回值1表示还要在readyqueue 里。最后总的处理 retval:
[cpp]view plaincopyprint?if (pending_requests)
get_request(server_s); switch(retval)
{ case -1: trailer =
/*request blocked */ current = current-
current; >next;
block_request(trailer); break;
case 0: /* request complete */
current->time_last = current_time; trailer
=current; current= current->next;
free_request(&request_ready,trailer);
break; case 1: current->time_last = current_time;
/*more to do */
current = current->next; default: | break; log_error_time(); |
fprintf(stderr, "Unknown retval in process.c - "
"Status: %d, retval: %d\n", current->status, retval); current = current->next;
break; }
每一轮最后检查一次,是否还有 pending_requests 。有的话 加入 ready_queue 。这次先到这儿,看起来还是挺头晕的。 留下 get_requests 没说,以后如果再碰到再说。
Copyright © 2019- aiwanbo.com 版权所有 赣ICP备2024042808号-3
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务