首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 網路程式設計:進程模型伺服器端演進(從 Fork 到靜態進程池)

     編輯:關於C語言

Linux 網路程式設計:進程模型伺服器端演進(從 Fork 到靜態進程池)

本文展示四種進程模型伺服器的演進路徑,並完整實作前兩種:每連接一進程 與 預先 Fork 靜態進程池,附帶驚群效應解決方案(檔案鎖)與訊號處理。

模型演進四境界

  1. 每連接一進程:accept 後 fork 子進程處理,處理完退出。
  2. 靜態進程池:啟動時預先 fork 固定數量子進程,共享 listen socket,競爭 accept。
  3. 動態進程池:根據負載動態增減子進程數量。
  4. 混合模型:結合執行緒池、I/O 多工等技術。

模型一:每連接一進程

優點

  • 程式邏輯極簡單,適合教學與低併發場景。

缺點

  • 頻繁 fork/exit 開銷大,無法應對高併發。

關鍵程式碼片段

void process_mode(int sock_fd, int connect_fd) {
    pid_t pid = fork();
    if (pid < 0) { perror("fork"); exit(errno); }
    if (pid == 0) { // 子進程
        close(sock_fd); // 關閉監聽 socket
        // 處理連接...
        close(connect_fd);
        exit(0);
    }
    // 父進程
    close(connect_fd); // 關閉連接 socket
}

注意:fork 後父子進程必須分別關閉不需要的 socket 描述符,否則會導致檔案描述符洩漏或連接無法正確關閉。

模型二:靜態進程池(預先 Fork)

核心難點:驚群效應

多個子進程同時阻塞在 accept,當新連接到來時,所有進程被喚醒但只有一個成功,其餘白白消耗 CPU。解決方案:檔案鎖序列化 accept。

鎖操作封裝

struct flock lock_it = {F_WRLCK, SEEK_SET, 0, 0};
struct flock unlock_it = {F_UNLCK, SEEK_SET, 0, 0};
int lock_fd = -1;

void my_lock_init(char *pathname) {
    char lock_file[1024];
    strncpy(lock_file, pathname, sizeof(lock_file));
    lock_fd = mkstemp(lock_file);
    unlink(lock_file); // 建立匿名臨時檔案
}

void my_lock_wait() {
    while (fcntl(lock_fd, F_SETLKW, &lock_it) < 0) {
        if (errno == EINTR) continue;
        perror("fcntl"); exit(errno);
    }
}

void my_lock_release() {
    if (fcntl(lock_fd, F_SETLKW, &unlock_it) < 0) {
        perror("fcntl"); exit(errno);
    }
}

子進程迴圈

void child_loop(int sock_fd) {
    int connect_fd;
    struct sockaddr_in client;
    socklen_t client_len;

    while (1) {
        my_lock_wait();
        client_len = sizeof(client);
        connect_fd = accept(sock_fd, (struct sockaddr*)&client, &client_len);
        printf("process %d deal with connector\n", getpid());
        process(connect_fd); // 業務邏輯
        close(connect_fd);
        my_lock_release();
    }
}

主程式建立進程池

void preprocess(int sock_fd, int n) {
    pool.nchild = n;
    pool.pids = malloc(n * sizeof(pid_t));
    my_lock_init("/tmp/lock.XXXXXX");
    for (int i = 0; i < n; i++)
        pool.pids[i] = make_child(sock_fd); // fork 並進入 child_loop
    signal(SIGINT, sig_call_back); // 捕捉 Ctrl+C 清理子進程
    for (;;) pause(); // 父進程等待訊號
}

客戶端測試程式

// 連線指定次數發送 "Hello World"
for (i = 0; i < atoi(argv[3]); i++)
    connect_server(argv[1], atoi(argv[2]));

總結與展望

  • 靜態進程池消除了頻繁 fork 開銷,但進程數固定,無法適應負載波動。
  • 父進程必須處理 SIGINT 並 kill 所有子進程,避免孤兒進程。
  • 下一步演進:動態進程池(依負載增減)、執行緒池(更輕量)、I/O 多工(epoll/kqueue,單進程高併發)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved