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

深度解析 fsockopen vs pfsockopen:持久連線效能實測與 Keep-Alive 機制

     編輯:PHP 綜合知識

前言

PHP 手冊僅說明:pfsockopen() 是持久連線版 fsockopen()。但什麼是持久?何時生效?本文透過實測代碼與網路層面分析,揭示兩者在 HTTP Keep-Alive 下的真實行為差異。

實測代碼

<?php
$data = "1,0,721,73,1,0,0,43290000,0,60D81D509BC00451,3,FFFFFFFF";
$host = '127.0.0.1';
$url  = "/aa.php";
$times = 1000;

// 測試 fsockopen
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    httpPost($host, $url, $data, false); // fsockopen
}
$fsTime = microtime(true) - $start;

// 測試 pfsockopen
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    httpPost($host, $url, $data, true); // pfsockopen
}
$pfsTime = microtime(true) - $start;

echo "fsockopen:  {$fsTime}s\n";
echo "pfsockopen: {$pfsTime}s\n";

function httpPost($host, $url, $data, $persistent)
{
    $func = $persistent ? 'pfsockopen' : 'fsockopen';
    $conn = $func($host, 80, $errno, $errstr, 30);
    if (!$conn) return "ERR: $errstr ($errno)";

    $header = "POST $url HTTP/1.1\r\n";
    $header .= "Host: $host\r\n";
    $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $header .= "Content-Length: " . strlen($data) . "\r\n";
    $header .= "Connection: Keep-Alive\r\n\r\n";
    $header .= "$data\r\n\r\n";

    fwrite($conn, $header);
    // 關鍵差異點:是否 fclose
    // fclose($conn); // 註解掉 vs 開啟
    return '';
}

實測結果分析

場景fsockopenpfsockopen結論
不呼叫 fclose() 11.05 秒 0.35 秒 pfsockopen 重用 TCP 連線,fsockopen 每次重建
呼叫 fclose() 12.51 秒 11.12 秒 fclose 強制斷開,pfsockopen 退化為 fsockopen

核心機制深度解析

fsockopen (非持久)

  1. 每次呼叫 → socket() → connect() → 三次交握 (SYN, SYN-ACK, ACK)。
  2. 傳送請求 → 接收回應。
  3. 腳本結束或 fclose() → close() → 四次揮手 (FIN, ACK, FIN, ACK)。
  4. **即使送出 Connection: Keep-Alive,PHP 仍會在請求結束後主動關閉連線**。

pfsockopen (持久)

  1. 首次呼叫 → 建立連線 → 存入**進程級連線池** (key: host:port)。
  2. 後續呼叫 → 從連線池取出現有連線 → 直接傳送資料 (跳過三次交握)。
  3. **關鍵**:fclose() **不會真正關閉底層 socket**,只是將連線歸還池中。
  4. 進程結束 (PHP-FPM worker 回收、Apache 子進程退出) 時,OS 回收 socket。

為何 Keep-Alive 需搭配 pfsockopen?

HTTP/1.1 預設 Keep-Alive,但 **PHP 的請求生命週期是「單請求單進程/執行緒」**。

  • Client (PHP) 發起請求 → Server 回應並保持連線。
  • PHP 腳本結束 → 進程/執行緒釋放 → **若用 fsockopen,socket 關閉,Server 端連線被動斷開**。
  • 下一請求 → PHP 新進程 → 無法使用上一連線 → 必須重新建立。
  • **pfsockopen 將 socket 存活期綁定到 PHP 進程**,跨越多個 HTTP 請求週期,真正實現 Client 端 Keep-Alive。

適用場景與避坑指南

✅ 適合 pfsockopen

  • 高頻呼叫同一上游 API (微服務間通訊、支付網關、推送服務)。
  • 長時間運行的 CLI 腳本 (Daemon、Worker)。
  • 需維持會話狀態的協定 (SMTP、IMAP、WebSocket 手工實作)。

❌ 不適合 / 風險

  • PHP-FPM / Apache Prefork:每個 worker 獨立進程,**連線池不共享**,併發高時連線數 = worker 數 × 目標主機數,易耗盡上游連線限制。
  • 目標服務重啟/斷線:池中殘留失效連線,下次取用會報錯,需實作重試邏輯 (檢查 feof()、fwrite() 回傳)。
  • 多執行緒環境 (ZTS):持久連線非執行緒安全,官方文檔明確不建議。

最佳實踐模板

function persistentHttpPost($host, $url, $data, $maxRetries = 2)
{
    for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
        $conn = @pfsockopen($host, 80, $errno, $errstr, 10);
        if (!$conn) {
            if ($attempt === $maxRetries) throw new Exception("Connect failed: $errstr");
            usleep(100000); continue;
        }

        $req = "POST $url HTTP/1.1\r\nHost: $host\r\nContent-Length: " . strlen($data) . "\r\nConnection: Keep-Alive\r\n\r\n$data";
        if (@fwrite($conn, $req) === false) {
            @fclose($conn); // 歸還池中 (標記失效)
            if ($attempt === $maxRetries) throw new Exception('Write failed');
            usleep(100000); continue;
        }

        $resp = '';
        stream_set_timeout($conn, 5);
        while (!feof($conn)) { $resp .= fgets($conn, 8192); }
        // 不 fclose!歸還池供下次重用

        // 簡易檢查連線是否存活
        if (strpos($resp, 'HTTP/') !== 0) {
            @fclose($conn); // 強制丟棄壞連線
            if ($attempt === $maxRetries) throw new Exception('Bad response');
            continue;
        }
        return $resp;
    }
}

現代替代方案

新專案建議使用:

  • Guzzle / Http\Client (PSR-18):內建連線池、重試、中介軟體、異步支援。
  • Swoole / OpenSwoole:協程級連線池、原生 Keep-Alive、高併發。
  • cURL 多執行緒:curl_multi_* 或 CURLOPT_PIPEWAIT 實現管線化。

結語

pfsockopen 透過進程級連線池實現 TCP 連線重用,在**單進程多請求** (CLI Daemon) 或 **Prefork 低併發** 場景下大幅降低延遲。但在現代 PHP-FPM 高併發架構下,連線池碎片化導致資源浪費,**建議遷移至成熟的 HTTP 客戶端庫**。

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