lighttpd是目前非常流行的web服務器,很多流量非常大的網(wǎng)站(如youtube)使用的就是lighttpd,它的代碼量不多,但是設(shè)計巧妙,效率高,功能完備(這是它將來能取代Apache的重要因素),編碼風格優(yōu)美, 是學習網(wǎng)絡(luò)編程,熟悉http服務器編寫的良好范例.在我初學網(wǎng)絡(luò)編程的時候,就是看的lighttpd的源碼進行學習,在其中學到了不少的技巧.我打算將這些寫出來與別人分享,可能開始比較雜亂,也不會作完全的分析,因為很多部分的代碼我也沒有看過,寫一點是一點吧.我進行閱讀和分析的lighttpd版本是1.4.18.
lighttpd采用的是多進程+多路復用(如select,epoll)的網(wǎng)絡(luò)模型,它對多路復用IO操作的封裝將作為下一個專題的內(nèi)容,本次將講解它所采用的多進程模型.
lighttpd中的配置文件有一項server.max-worker配置的是服務器生成的工作進城數(shù).在lighttpd中, 服務器主進程被稱為watcher(監(jiān)控者),而由這個主進程創(chuàng)建出來的子進程被稱為woker(工作者),而woker的數(shù)量正是由上面提到的配置項進行配置的.watcher創(chuàng)建并且監(jiān)控woker的代碼如下所示,我覺得這是一段很巧妙的代碼,在我閱讀了這段代碼之后,到目前為止,我所寫的所有服務器采用的都是類似lighttpd的watcher-woker多進程模型,這段代碼在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是一個標志位, 為0時是父進程(watcher), 為1時則是子進程(worker), 下面進入一個循環(huán):
while (!child && !srv_shutdown && !graceful_shutdown)
也就是說只要是父進程而且服務器沒有被關(guān)閉該循環(huán)就一直進行下去, 接著如果num_childs>0就執(zhí)行fork函數(shù)創(chuàng)建子進程, 對于子進程而言,賦值child=1, 同時num_childs值-1, 于是子進程不滿足!child這個條件退出循環(huán)繼續(xù)執(zhí)行下面的代碼, 而父進程還在循環(huán)中, 如果num_childs=0也就是所有的子進程都創(chuàng)建成功了, 那么父進程就阻塞在調(diào)用wait函數(shù)中, 等待著一旦有子進程退出, 那么wait函數(shù)返回, 這樣num_childs+1, 于是繼續(xù)前面的調(diào)用, 再次創(chuàng)建出子進程.
這也就是watcher和worker的來歷:父進程負責創(chuàng)建子進程并且監(jiān)控是否有子進程退出, 如果有, 那么再次創(chuàng)建出子進程;而子進程是worker, 是具體執(zhí)行服務器操作的工作者, 在被創(chuàng)建完畢之后退出循環(huán), 去做下面的事情.而如果父進程退出這個循環(huán), 那么一定是srv_shutdown或者graceful_shutdown之一變?yōu)榱朔橇阒? 所以在循環(huán)外, 還要進行判斷, 如果是父進程, 那么就是服務器程序要退出了, 最后作一些清理的工作.
用偽碼表示這部分代碼就是:
如果(是父進程 而且 當前沒有要求終止服務器) 就一直循環(huán)下去
{
如果還有未創(chuàng)建的子進程
{
創(chuàng)建出一個子進程
如果是子進程, 那么根據(jù)最上面的循環(huán)條件退出這個循環(huán).
如果是父進程, 那么將未創(chuàng)建的子進程數(shù)量 - 1
}
否則 就是沒有未創(chuàng)建的子進程
{
一直保持睡眠, 一旦發(fā)現(xiàn)有子進程退出父進程就蘇醒, 將未創(chuàng)建的子進程數(shù)量 + 1;
}
}
父進程的代碼永遠不會執(zhí)行到這個循環(huán)體之外, 一旦發(fā)生, 就是因為要終止服務器的運行, 如果這種情況發(fā)生, 就進行最后的一些清理工作....
在這之后, 各子進程分道揚鑣, 各自去進行自己的工作, 互不干擾.這也是我非常喜歡多進程編程的原因, 少了多線程編程中考慮到數(shù)據(jù)同步等麻煩的事情,要考慮的事情相對而言簡單的多了.
關(guān)于多進程 VS 多線程的話題, 不在這里多加闡述了.