BOA代码笔记 4.docx
- 文档编号:29380621
- 上传时间:2023-07-22
- 格式:DOCX
- 页数:7
- 大小:18.73KB
BOA代码笔记 4.docx
《BOA代码笔记 4.docx》由会员分享,可在线阅读,更多相关《BOA代码笔记 4.docx(7页珍藏版)》请在冰豆网上搜索。
BOA代码笔记4
BOA代码笔记4
main.c(完?
)
从上次继续
上次我们看到了这个地方:
[cpp]viewplaincopyprint?
if(max_connections<1){structrlimitrl;/*hasnotbeensetexplicitly*/c=getrlimit(RLIMIT_NOFILE,&rl);if(c<0){perror("getrlimit");exit
(1);}max_connections=rl.rlim_cur;}/*backgroundourself*/if(do_fork){switch(fork()){case-1:
/*error*/perror("fork");exit
(1);break;case0:
/*child,success*/break;default:
/*parent,success*/exit(0);break;}}/*mainloop*/timestamp();status.requests=0;status.errors=0;start_time=current_time;select_loop(server_s);return0;
第一个if块确定最大连接数。
如果未指定,使用getrlimit来获得。
getrlimit(RLIMIT_NOFILE,&rl);specifiesavalueonegreaterthanthemaximumfiledescriptornumberthatcanbeopenedbythisprocess.第二个if块,如果do_fork为1,那么我们从子进程运行,父进程结束。
timestamp就是一段小程序:
[cpp]viewplaincopyprint?
voidtimestamp(void){log_error_time();fprintf(stderr,"boa:
serverversion%s\n",SERVER_VERSION);log_error_time();fprintf(stderr,"boa:
serverbuilt"__DATE__"at"__TIME__".\n");log_error_time();fprintf(stderr,"boa:
startingserverpid=%d,port%d\n",(int)getpid(),server_port);}
程序之前好像已经将stderr重定向到自定义的错误处理文件中了。
然后,清空status的两个域,记录一下start_time。
然后就开始select_loop了~select_loop()ps:
博主没啥经验,并未通读过源码,这也是第一次看正经点儿的源代码。
这篇写完最后仍然云里雾里,较多猜测。
如有错误欢迎指正。
如果想知道代码背后的思想流程架构,那就得等博主看完全部代码再总结了(如果我有这能力和耐心的话……)。
ps2:
如果由于博主没有全局把握,看到哪儿讲哪儿的方式引起你的极度不适,我表示非常抱歉。
并强烈推荐你自己去看源码,也许效果拔群。
:
)
先粘一下select_loop的代码:
[cpp]viewplaincopyprint?
voidselect_loop(intserver_s){FD_ZERO(&block_read_fdset);FD_ZERO(&block_write_fdset);/*setserver_sandreq_timeout*/req_timeout.tv_sec=(ka_timeout?
ka_timeout:
REQUEST_TIMEOUT);req_timeout.tv_usec=0l;/*resettimeout*//*presetmax_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){sigterm_stage2_run();}}/*resetmax_fd*/max_fd=-1;if(request_block)/*moveselectedreq'sfromrequest_blocktorequest_ready*/fdset_update();/*anyblockedreq'smovefromrequest_readytorequest_block*/process_requests(server_s);if(!
sigterm_flag&&total_connections<(max_connections-10)){BOA_FD_SET(server_s,&block_read_fdset);/*serveralwaysset*/}req_timeout.tv_sec=(request_ready?
0:
(ka_timeout?
ka_timeout:
REQUEST_TIMEOUT));req_timeout.tv_usec=0l;/*resettimeout*/if(select(max_fd+1,&block_read_fdset,&block_write_fdset,NULL,(request_ready||request_block?
&req_timeout:
NULL))==-1){/*whatistheappropriatethingtodohereonEBADF*/if(errno==EINTR)continue;/*while
(1)*/elseif(errno!
=EBADF){DIE("select");}}time(¤t_time);if(FD_ISSET(server_s,&block_read_fdset))pending_requests=1;}}
清空block_read_fdset和block_write_fdset,这两个看名字我猜是用在select里,具体用来表示哪些fd的集合以后才知道。
req_timeout用作select的时间限制,#defineREQUEST_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后错误访问时会产生,XX一下发现,在一些体系结构上,访问未对齐的地址会产生。
对于SIGINT,收到这个信号时,记录一下,正常退出。
这个信号可以由ctrl+c发送给foregroundprocess产生。
剩下了这四个在while循环里处理的信号。
SIGHUP用来重新读取config_file。
先清空fdset,清空read_config_file里动态分配的内存,清空request_free链表,然后调用read_config_file。
对于SIGCHLD的处理是典型的子进程处理方式,UNP里有总结,如下:
[cpp]viewplaincopyprint?
sigchld_flag=0;while((pid=waitpid(-1,&status,WNOHANG))>0)if(verbose_cgi_logs){time(¤t_time);log_error_time();fprintf(stderr,"reapingchild%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)
/*moveselectedreq'sfromrequest_blocktorequest_ready*/
fdset_update();源代码里对函数fdset_update();的说明如下:
/*
*Name:
fdset_update
*
*Description:
iteratethroughtheblockedrequests,checkingwhether
*thatfiledescriptorhasbeensetbyselect.Updatethefd_setto
*reflectcurrentstatus.
*
*Here,weneedtodosomethings:
*-keepalivetimeoutssimplyclose
*(thisisspecial:
:
akeepalivetimeoutisatimeoutwhere
keepaliveisactivebutnothinghasbeenreadyet)
*-regulartimeoutsclose+error
*-stuffinbufferandfdready?
writeitout
*-fdreadyforotheractions?
dothem
*/一句话总结:
fdset_update将合适的request从block链表里移动到ready链表里。
boa里边有三个请求链表
request*request_ready=NULL;/*readylisthead*/
request*request_block=NULL;/*blockedlisthead*/
request*request_free=NULL;/*freelisthead*/新的连接需要reqeust结构体时优先从request_free中提取。
如果为空,将malloc一个reqeust。
简单的描述一下,fdset_update进行如下处理:
首先,获取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注释为/*fdforpostdatatmpfile*/,应该是客户端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:
Iteratesthroughthereadyqueue,passingeachrequest
*totheappropriatehandlerforprocessing.Itmonitorsthe
*returnvaluefromhandlerfunctions,allofwhichreturn-1
*toindicateablock,0oncompletionand1toremainonthe
*readylistformoreprocesing.
*/对于每一个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,orbytesleft。
然后对retval做规范处理。
其他状态处理如下:
[cpp]viewplaincopyprint?
switch(current->status){caseREAD_HEADER:
caseONE_CR:
caseONE_LF:
caseTWO_CR:
retval=read_header(current);break;caseBODY_READ:
retval=read_body(current);break;caseBODY_WRITE:
retval=write_body(current);break;caseWRITE:
retval=process_get(current);break;casePIPE_READ:
retval=read_from_pipe(current);break;casePIPE_WRITE:
retval=write_from_pipe(current);break;caseDONE:
/*anon-statusthatwillterminatetherequest*/retval=req_flush(current);/**retvalcanbe-2=error,-1=blocked,orbytesleft*/if(retval==-2){/*error*/current->status=DEAD;retval=0;}elseif(retval>0){retval=1;}break;caseDEAD:
retval=0;current->buffer_end=0;SQUASH_KA(current);break;default:
retval=0;fprintf(stderr,"Unknownstatus(%d),""closing!
\n",current->status);current->status=DEAD;break;}
每个状态的处理函数可能有自己的返回值,个别的需要规范化处理,以尽量满足:
返回值-1表示需要进入blockqueue;返回值0表示请求结束;返回值1表示还要在readyqueue里。
最后总的处理retval:
[cpp]viewplaincopyprint?
if(pending_requests)get_request(server_s);switch(retval){case-1:
/*requestblocked*/trailer=current;current=current->next;block_request(trailer);break;case0:
/*requestcomplete*/current->time_last=current_time;trailer=current;current=current->next;free_request(&request_ready,trailer);break;case1:
/*moretodo*/current->time_last=current_time;current=current->next;break;default:
log_error_time();fprintf(stderr,"Unknownretvalinprocess.c-""Status:
%d,retval:
%d\n",current->status,retval);current=current->next;break;}
每一轮最后检查一次,是否还有pending_requests。
有的话加入ready_queue。
这次先到这儿,看起来还是挺头晕的。
留下get_requests没说,以后如果再碰到再说。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- BOA代码笔记 BOA 代码 笔记