PHP 提供兩個建立 Socket 連線的函式:fsockopen() 與 pfsockopen()。官方文件僅說明後者為「持久連線」,但實際行為差異在何處?本文透過實測代碼與結果分析,揭示兩者在 Keep-Alive 條件下的真實表現,並給出選擇建議。
// 非持久連線
resource fsockopen ( string $hostname , int $port = -1 , int &$errno , string &$errstr , float $timeout = ini_get("default_socket_timeout") )
// 持久連線(進程級別重用)
resource pfsockopen ( string $hostname , int $port = -1 , int &$errno , string &$errstr , float $timeout = ini_get("default_socket_timeout") )
關鍵差異: pfsockopen() 建立的連線在請求結束後**不會關閉**,而是保留在 PHP 進程的連線池中,下一次同進程呼叫相同主機/埠時可直接重用,省去 TCP 三向交握與 SSL 握手開銷。
<?php
// 測試環境:本地回環 127.0.0.1:80,模擬 HTTP POST 請求
$data = "1,0,721,73,1,0,0,43290000,0,60D81D509BC00451,3,FFFFFFFF";
$host = '127.0.0.1';
$url = '/aa.php';
$times = 1000;
// 封裝發送函式
function httpPost($host, $url, $data, $persistent) {
$func = $persistent ? 'pfsockopen' : 'fsockopen';
$conn = $func($host, 80, $errno, $errstr, 30);
if (!$conn) return "Error: $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);
// 這裡故意不讀取回應、不關閉連線,模擬單向推送場景
// 實際專案務必處理回應並根據協議決定是否關閉
return $conn;
}
// 執行測試
$start = microtime(true);
for ($i=0; $i<$times; $i++) httpPost($host, $url, $data, false);
$mid = microtime(true);
for ($i=0; $i<$times; $i++) httpPost($host, $url, $data, true);
$end = microtime(true);
echo "fsockopen: " . ($mid - $start) . " 秒\n";
echo "pfsockopen: " . ($end - $mid) . " 秒\n";
?>
| 場景 | fsockopen 耗時 | pfsockopen 耗時 | 差異倍數 |
|---|---|---|---|
| 不關閉連線(註解 fclose) | 11.05 秒 | 0.35 秒 | ~31x |
| 正常關閉連線(執行 fclose) | 12.51 秒 | 11.12 秒 | ~1.1x |
pfsockopen 完美重用連線,僅第一次建立連線,後續 999 次直接複用,效能提升 **30 倍以上**。fclose(),持久連線無法發揮優勢。Connection: Keep-Alive,PHP 仍會在腳本結束或 fclose() 時切斷 TCP 連線,無法跨請求重用。fclose() 時,發送 FIN 封包關閉連線。host:port。max_requests)時才真正關閉。while (!feof($conn)) fgets($conn); 讀完。stream_get_meta_data($conn)['eof'] 或嘗試讀取 1 位元組偵測斷線,失敗則 fclose() 重建。stream_socket_client() 配合 STREAM_CLIENT_PERSISTENT 旗標,控制更精細。netstat -an | grep ESTABLISHED 中持久連線數量,防止連線洩漏導致後端拒絕服務。| 使用場景 | 推薦函式 | 理由 |
|---|---|---|
| 單次/低頻 API 呼叫、目標主機多變 | fsockopen | 簡單、無狀態、無連線洩漏風險 |
| 高頻呼叫固定後端(微服務、Redis、DB Proxy) | pfsockopen | 省去握手開銷,吞吐量提升 10x~30x |
| 需精細控制連線生命週期、非阻塞、SSL 客戶端憑證 | stream_socket_client + STREAM_CLIENT_PERSISTENT | 功能最完整,可配合 stream_context 設定 TLS 參數 |
總結:pfsockopen 是高頻同質請求的利器,但需配合完善的回應讀取、斷線重連與監控機制。盲目替換 fsockopen 反而可能引入難以排查的協議錯亂與連線洩漏。建議在壓測驗證後,針對核心高頻路徑選擇性採用。