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

PHP 多進程程式設計完整實作:pcntl_fork 與 Socket 通訊範例

     編輯:PHP 綜合知識

前言

PHP 透過 pcntl 擴展支援進程控制,可實作多進程常駐服務(Daemon)。本文提供一個完整的 **TCP 代理伺服器** 範例,展示 pcntl_fork、訊號處理、非阻塞 Socket、子進程管理等核心技術。

環境需求

  • Linux/Unix 環境(Windows 不支援 pcntl)
  • PHP 編譯啟用 --enable-pcntl、--enable-sockets
  • CLI 模式執行(非 Web 伺服器環境)

完整程式碼結構解析

以下為精簡重點的架構說明,完整可執行代碼請參考原文。

1. 訊號處理機制

<?php
// 父進程訊號處理
function parentSignalHandler($signo) {
    global $childPid;
    switch ($signo) {
        case SIGTERM:
        case SIGINT:
            if ($childPid) {
                posix_kill($childPid, SIGTERM); // 通知子進程結束
                pcntl_wait($status);            // 回收子進程,避免殭屍進程
            }
            closeSockets();
            exit(0);
        case SIGCHLD:
            // 子進程結束時自動觸發
            $childPid = null;
            pcntl_wait($status);
            break;
    }
}

// 子進程訊號處理
function childSignalHandler($signo) {
    if (in_array($signo, [SIGTERM, SIGINT])) {
        closeSockets();
        exit(0);
    }
}
?>

2. Daemon 化流程

<?php
// 第一次 fork:父進程退出,子進程成為孤兒進程,由 init 收養
$pid = pcntl_fork();
if ($pid === -1) die('fork 失敗');
if ($pid > 0) exit(0); // 父進程退出

// 脫離終端機,成為 session leader
if (posix_setsid() === -1) die('無法脫離終端機');

// 第二次 fork(可選):確保無法重新獲得控制終端
$pid = pcntl_fork();
if ($pid === -1) die('fork 失敗');
if ($pid > 0) exit(0);

// 現在是完整的 Daemon 進程
?>

3. 非阻塞 Socket 事件迴圈

<?php
function processClient($clientSocket) {
    global $remoteHost, $remotePort;
    
    declare(ticks = 1); // 必須:啟用訊號處理
    pcntl_signal(SIGTERM, 'childSignalHandler');
    pcntl_signal(SIGINT, 'childSignalHandler');

    // 連線至上游伺服器
    $remoteSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_connect($remoteSocket, $remoteHost, $remotePort);
    
    // 設為非阻塞
    socket_set_nonblock($clientSocket);
    socket_set_nonblock($remoteSocket);

    while (true) {
        $clientData = @socket_read($clientSocket, 4096, PHP_BINARY_READ);
        $remoteData = @socket_read($remoteSocket, 4096, PHP_BINARY_READ);

        // 處理連線關閉
        if ($clientData === '' || $remoteData === '') break;
        
        // 非阻塞讀取無資料時回傳 false
        $clientReady = $clientData !== false;
        $remoteReady = $remoteData !== false;
        
        if (!$clientReady && !$remoteReady) {
            usleep(100000); // 0.1 秒降低 CPU 佔用
            continue;
        }

        // 雙向轉發資料
        if ($clientReady) {
            writeAll($remoteSocket, $clientData);
        }
        if ($remoteReady) {
            writeAll($clientSocket, $remoteData);
        }
    }
    
    closeSockets();
    exit(0);
}

function writeAll($socket, $data) {
    $written = 0;
    $len = strlen($data);
    while ($written < $len) {
        $n = @socket_write($socket, substr($data, $written), $len - $written);
        if ($n === false) {
            usleep(100000);
            continue;
        }
        if ($n === 0) break; // 連線中斷
        $written += $n;
    }
}
?>

4. 主迴圈:監聽並 Fork 子進程處理連線

<?php
$listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($listenSocket, '0.0.0.0', 9999);
socket_listen($listenSocket);
socket_set_nonblock($listenSocket);

pcntl_signal(SIGTERM, 'parentSignalHandler');
pcntl_signal(SIGINT, 'parentSignalHandler');
pcntl_signal(SIGCHLD, 'parentSignalHandler');

$childPid = null;

while (true) {
    sleep(1); // 簡單輪詢,生產環境建議用 socket_select/poll/epoll
    
    $clientSocket = @socket_accept($listenSocket);
    if ($clientSocket !== false) {
        $pid = pcntl_fork();
        if ($pid === -1) die('fork 失敗');
        if ($pid > 0) {
            // 父進程:關閉客戶端 socket,繼續監聽
            socket_close($clientSocket);
            $childPid = $pid;
            continue;
        }
        
        // 子進程:處理連線
        processClient($clientSocket);
    }
}
?>

關鍵技術點總結

技術關鍵點
pcntl_fork複製進程空間,父進程回傳子 PID,子進程回傳 0
declare(ticks=1)啟用 PHP 訊號處理機制(註冊 pcntl_signal 後必須)
非阻塞 Socketsocket_set_nonblock + 輪詢/事件驅動,避免單進程阻塞
殭屍進程防止父進程 SIGCHLD 處理器中呼叫 pcntl_wait()
優雅關閉捕捉 SIGTERM/SIGINT,通知子進程、回收資源、關閉 Socket
資料完整轉發socket_write 可能部分寫入,需迴圈確保全數發送

現代替代方案建議

純 PHP 多進程開發複雜度高、易出錯,生產環境建議考慮:

  • Swoole / OpenSwoole:內建多進程/協程、事件驅動、高效能網路框架
  • Workerman:純 PHP、易上手、支援多進程、長連線
  • ReactPHP / Amp:事件驅動、非阻塞 I/O、單進程高併發
  • PHP-FPM + 消息佇列:將耗時任務推至 RabbitMQ/Redis,Worker 消費

本範例適合學習底層原理、維護舊系統、或極簡環境無法安裝擴展時使用。新專案強烈建議採用上述成熟框架。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved