lighttpd 採用經典的 Watcher-Worker 多進程模型,其核心思想極為簡潔:一個主進程負責監控,多個工作進程負責處理實際連接。這種架構在高併發場景下表現出色,兼具穩定性與擴展性。本文將深入剖析 lighttpd 1.4.20 的源碼,揭示其進程管理、信號處理與事件循環的核心邏輯。
程序入口位於 server.c 的 main 函數。啟動初期主要處理參數解析與初始化,有兩處關鍵邏輯值得關注:
if (test_config) {
printf("Syntax OK\n");
}
代碼顯示,當啟用 test_config 標誌時,程序僅輸出 "Syntax OK",並未執行實際的配置解析與校驗。這在源碼中屬於預留接口,實際部署時需注意此限制。
daemonize() 函數採用教科書級的 Unix 守護進程創建流程:
static void daemonize(void) {
// 忽略終端相關信號
#ifdef SIGTTOU signal(SIGTTOU, SIG_IGN); #endif
#ifdef SIGTTIN signal(SIGTTIN, SIG_IGN); #endif
#ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); #endif
if (0 != fork()) exit(0); // 第一次 fork,父進程退出
if (-1 == setsid()) exit(0); // 創建新會話
signal(SIGHUP, SIG_IGN); // 忽略掛起信號
if (0 != fork()) exit(0); // 第二次 fork,確保無法重新獲得控制終端
if (0 != chdir("/")) exit(0); // 切換工作目錄至根目錄
}
雙重 Fork 機制確保進程完全脫離終端控制,符合 APUE 中守護進程編程的最佳實踐。
初始化完成後,主進程進入核心 while 循環,實現進程池的動態管理:
int child = 0;
while (!child && !srv_shutdown && !graceful_shutdown) {
if (num_childs > 0) {
// Watcher 階段:持續生成 Worker
switch (fork()) {
case -1: return -1; // 錯誤
case 0: child = 1; break; // 子進程跳出循環
default: num_childs--; break; // 父進程繼續
}
} else {
// Watcher 監控階段:回收退出的子進程
int status;
if (-1 != wait(&status)) {
num_childs++; // 有 Worker 退出,增加配額以觸發重生
} else {
switch (errno) {
case EINTR:
if (handle_sig_hup) {
handle_sig_hup = 0;
log_error_cycle(srv);
if (!forwarded_sig_hup) {
forwarded_sig_hup = 1;
kill(0, SIGHUP); // 向進程組廣播 SIGHUP
}
}
break;
}
}
}
}
num_childs 控制 Worker 數量,配置文件可調整。Worker 異常退出時,Watcher 檢測到 wait() 返回,遞增計數器,下一輪循環即會 Fork 新進程補位。SIGHUP(如日誌輪轉)時,Watcher 調用 kill(0, SIGHUP) 向整個進程組廣播,實現優雅重啟。child 標誌區分身份,子進程跳出循環轉入事件處理,父進程永駐循環履行監護職責。子進程脫離 Watcher 循環後,初始化 fdevent(文件描述符事件系統)、stat cache 等組件,進入主事件循環:
stat cache。超時判斷採用輪詢所有連接,比較 idle 時間與 max-idle 閾值,超時連接標記為 CON_STATE_ERROR。sockets_disabled 標誌置位(過載保護觸發),重新註冊所有連接到事件系統。fdevent_poll 等待 I/O 事件。do {
fdevent_handler handler;
void *context;
handler_t r;
fd_ndx = fdevent_event_next_fdndx(srv->ev, fd_ndx);
revents = fdevent_event_get_revent(srv->ev, fd_ndx);
fd = fdevent_event_get_fd(srv->ev, fd_ndx);
handler = fdevent_get_handler(srv->ev, fd);
context = fdevent_get_context(srv->ev, fd);
switch (r = (*handler)(srv, context, revents)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD: break;
case HANDLER_ERROR: SEGFAULT(); break;
default: log_error_write(srv, __FILE__, __LINE__, "d", r);
}
} while (--n > 0);
這段代碼展示了 lighttpd 優雅的回調函數架構:事件層僅負責分發,具體業務邏輯完全封裝在 Handler 中。這種設計實現了關注點分離,代碼清晰且擴展性強。
值得注意的是,連接超時檢測採用每秒全量輪詢,在連接數極大時會帶來 CPU 開銷。但總體而言,Watcher-Worker 模型憑藉其簡潔的進程管理、高效的事件驅動架構,在實際生產環境中表現優異。這再次驗證了「簡單不代表低效」的系統設計哲學。