lighttpd是目前非常流行的web服務(wù)器,很多流量非常大的網(wǎng)站(如youtube)使用的就是lighttpd,它的代碼量不多,但是設(shè)計(jì)巧妙,效率高,功能完備(這是它將來能取代Apache的重要因素),編碼風(fēng)格優(yōu)美, 是學(xué)習(xí)網(wǎng)絡(luò)編程,熟悉http服務(wù)器編寫的良好范例.在我初學(xué)網(wǎng)絡(luò)編程的時候,就是看的lighttpd的源碼進(jìn)行學(xué)習(xí),在其中學(xué)到了不少的技巧.我打算將這些寫出來與別人分享,可能開始比較雜亂,也不會作完全的分析,因?yàn)楹芏嗖糠值拇a我也沒有看過,寫一點(diǎn)是一點(diǎn)吧.我進(jìn)行閱讀和分析的lighttpd版本是1.4.18.
lighttpd采用的是多進(jìn)程+多路復(fù)用(如select,epoll)的網(wǎng)絡(luò)模型,它對多路復(fù)用IO操作的封裝將作為下一個專題的內(nèi)容,本次將講解它所采用的多進(jìn)程模型.
lighttpd中的配置文件有一項(xiàng)server.max-worker配置的是服務(wù)器生成的工作進(jìn)城數(shù).在lighttpd中, 服務(wù)器主進(jìn)程被稱為watcher(監(jiān)控者),而由這個主進(jìn)程創(chuàng)建出來的子進(jìn)程被稱為woker(工作者),而woker的數(shù)量正是由上面提到的配置項(xiàng)進(jìn)行配置的.watcher創(chuàng)建并且監(jiān)控woker的代碼如下所示,我覺得這是一段很巧妙的代碼,在我閱讀了這段代碼之后,到目前為止,我所寫的所有服務(wù)器采用的都是類似lighttpd的watcher-woker多進(jìn)程模型,這段代碼在src目錄中server.c文件的main函數(shù)中:
#ifdef HAVE_FORK
/* start watcher and workers */
num_childs = srv->srvconf.max_worker;
if (num_childs > 0) {
int child = 0;
while (!child && !srv_shutdown && !graceful_shutdown) {
if (num_childs > 0) {
switch (fork()) {
case -1:
return -1;
case 0:
child = 1;
break;
default:
num_childs--;
break;
}
} else {
int status;
if (-1 != wait(&status)) {
/**
* one of our workers went away
*/
num_childs++;
} else {
switch (errno) {
case EINTR:
/**
* if we receive a SIGHUP we have to close our logs ourself as we don't
* have the mainloop who can help us here
*/
if (handle_sig_hup) {
handle_sig_hup = 0;
log_error_cycle(srv);
/**
* forward to all procs in the process-group
*
* we also send it ourself
*/
if (!forwarded_sig_hup) {
forwarded_sig_hup = 1;
kill(0, SIGHUP);
}
}
break;
default:
break;
}
}
}
}
/**
* for the parent this is the exit-point
*/
if (!child) {
/**
* kill all children too
*/
if (graceful_shutdown) {
kill(0, SIGINT);
} else if (srv_shutdown) {
kill(0, SIGTERM);
}
log_error_close(srv);
network_close(srv);
connections_free(srv);
plugins_free(srv);
server_free(srv);
return 0;
}
}
#endif
首先,woker的數(shù)量保存在變量num_childs中, 同時另一個變量child是一個標(biāo)志位, 為0時是父進(jìn)程(watcher), 為1時則是子進(jìn)程(worker), 下面進(jìn)入一個循環(huán):
while (!child && !srv_shutdown && !graceful_shutdown)
也就是說只要是父進(jìn)程而且服務(wù)器沒有被關(guān)閉該循環(huán)就一直進(jìn)行下去, 接著如果num_childs>0就執(zhí)行fork函數(shù)創(chuàng)建子進(jìn)程, 對于子進(jìn)程而言,賦值child=1, 同時num_childs值-1, 于是子進(jìn)程不滿足!child這個條件退出循環(huán)繼續(xù)執(zhí)行下面的代碼, 而父進(jìn)程還在循環(huán)中, 如果num_childs=0也就是所有的子進(jìn)程都創(chuàng)建成功了, 那么父進(jìn)程就阻塞在調(diào)用wait函數(shù)中, 等待著一旦有子進(jìn)程退出, 那么wait函數(shù)返回, 這樣num_childs+1, 于是繼續(xù)前面的調(diào)用, 再次創(chuàng)建出子進(jìn)程.
這也就是watcher和worker的來歷:父進(jìn)程負(fù)責(zé)創(chuàng)建子進(jìn)程并且監(jiān)控是否有子進(jìn)程退出, 如果有, 那么再次創(chuàng)建出子進(jìn)程;而子進(jìn)程是worker, 是具體執(zhí)行服務(wù)器操作的工作者, 在被創(chuàng)建完畢之后退出循環(huán), 去做下面的事情.而如果父進(jìn)程退出這個循環(huán), 那么一定是srv_shutdown或者graceful_shutdown之一變?yōu)榱朔橇阒? 所以在循環(huán)外, 還要進(jìn)行判斷, 如果是父進(jìn)程, 那么就是服務(wù)器程序要退出了, 最后作一些清理的工作.
用偽碼表示這部分代碼就是:
如果(是父進(jìn)程 而且 當(dāng)前沒有要求終止服務(wù)器) 就一直循環(huán)下去
{
如果還有未創(chuàng)建的子進(jìn)程
{
創(chuàng)建出一個子進(jìn)程
如果是子進(jìn)程, 那么根據(jù)最上面的循環(huán)條件退出這個循環(huán).
如果是父進(jìn)程, 那么將未創(chuàng)建的子進(jìn)程數(shù)量 - 1
}
否則 就是沒有未創(chuàng)建的子進(jìn)程
{
一直保持睡眠, 一旦發(fā)現(xiàn)有子進(jìn)程退出父進(jìn)程就蘇醒, 將未創(chuàng)建的子進(jìn)程數(shù)量 + 1;
}
}
父進(jìn)程的代碼永遠(yuǎn)不會執(zhí)行到這個循環(huán)體之外, 一旦發(fā)生, 就是因?yàn)橐K止服務(wù)器的運(yùn)行, 如果這種情況發(fā)生, 就進(jìn)行最后的一些清理工作....
在這之后, 各子進(jìn)程分道揚(yáng)鑣, 各自去進(jìn)行自己的工作, 互不干擾.這也是我非常喜歡多進(jìn)程編程的原因, 少了多線程編程中考慮到數(shù)據(jù)同步等麻煩的事情,要考慮的事情相對而言簡單的多了.
關(guān)于多進(jìn)程 VS 多線程的話題, 不在這里多加闡述了.