Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 網路編程:迭代服務器 vs 並發服務器核心差異與 Fork 陷阱

編輯:Linux 教學

前言

網路服務器架構最基礎的分野在於迭代與並發。本文以偽代碼對比兩種模型,重點剖析 fork() 並發模型中文件描述符引用計數導致的經典陷阱:父進程必須關閉 connfd、子進程必須關閉 listenfd,否則將導致連接洩漏、描述符耗盡、服務不可用。

一、迭代服務器:單線程循環處理

while (1) {
    int connfd = accept(listenfd, ...);  // 阻塞等待連接
    handle_request(connfd);              // 處理完才能接下一個
    close(connfd);
}
  • 優點:邏輯簡單、無併發競爭、調試容易。
  • 缺點:吞吐極低,單一慢請求阻塞所有新連接,生產環境極少用。

二、並發服務器:fork 子進程處理

while (1) {
    int connfd = accept(listenfd, ...);
    if (fork() == 0) {          // 子進程
        close(listenfd);        // 【關鍵】子進程不需監聽
        handle_request(connfd);
        close(connfd);
        exit(0);
    }
    close(connfd);              // 【關鍵】父進程不持有連接 fd
}

三、為什麼必須雙向關閉?—— 引用計數機制

核心原理

  • fork() 會複製父進程文件描述符表,子進程獲得的 fd 與父進程指向同一個 struct file 內核對象。
  • struct file 內含 f_count 引用計數,fork() 時 +1,close() 時 -1,歸零才真正釋放內核資源(斷開 TCP 連接、釋放 socket 緩衝區)。

場景推演

錯誤做法後果
父進程不 close(connfd)f_count 始終 ≥1,連接永不釋放 → 客戶端 FIN 後服務端仍保持 ESTABLISHED → 描述符耗盡、新連接被拒
子進程不 close(listenfd)浪費 fd;更嚴重:若父進程意外退出,子進程仍持有 listenfd → 端口被佔用、重啟失敗;多子進程同時持有 listenfd 可能引發 驚群問題(內核 4.5+ 已優化)

四、進階:僵屍進程與 SIGCHLD

父進程若不 wait(),子進程退出後變為殭屍(Z 狀態),佔用 task_struct 與 PID。解決方案:

signal(SIGCHLD, SIG_IGN);  // Linux 內核自動回收(推薦)
// 或
void sigchld_handler(int sig) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}
signal(SIGCHLD, sigchld_handler);

五、現代替代方案

模型特點適用場景
prefork / worker 池主進程 fork 固定數量 worker,共享 listenfd,accept 序列化Apache、Nginx、高併發短連接
線程池pthread + 共享內存,輕量上下文切換計算密集、需共享大量狀態
事件驅動 + 非阻塞 IO單線程 epoll/kqueue/IOCP,零切換、百萬併發Nginx、Redis、Node.js、高併發長連接
IO_uringLinux 5.1+,真異步、零拷貝、批量提交極致性能存儲/網路

總結

並發服務器 fork() 模型雖經典,但父關 connfd、子關 listenfd 是鐵律,源於 struct file 引用計數機制。理解此機制,不僅能避免「描述符洩漏、端口佔用、驚群」等經典坑,更能舉一反三應對 dup()、線程共享 fd、exec() 繼承 fd 等場景。現代高併發架構多已轉向事件驅動或混合模型,但文件描述符生命週期管理的核心邏輯始終不變。

Copyright © Windows教程網 All Rights Reserved