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 '';
}
| 場景 | fsockopen | pfsockopen | 結論 |
|---|---|---|---|
| 不呼叫 fclose() | 11.05 秒 | 0.35 秒 | pfsockopen 重用 TCP 連線,fsockopen 每次重建 |
| 呼叫 fclose() | 12.51 秒 | 11.12 秒 | fclose 強制斷開,pfsockopen 退化為 fsockopen |
socket() → connect() → 三次交握 (SYN, SYN-ACK, ACK)。fclose() → close() → 四次揮手 (FIN, ACK, FIN, ACK)。Connection: Keep-Alive,PHP 仍會在請求結束後主動關閉連線**。fclose() **不會真正關閉底層 socket**,只是將連線歸還池中。HTTP/1.1 預設 Keep-Alive,但 **PHP 的請求生命週期是「單請求單進程/執行緒」**。
feof()、fwrite() 回傳)。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;
}
}
新專案建議使用:
curl_multi_* 或 CURLOPT_PIPEWAIT 實現管線化。pfsockopen 透過進程級連線池實現 TCP 連線重用,在**單進程多請求** (CLI Daemon) 或 **Prefork 低併發** 場景下大幅降低延遲。但在現代 PHP-FPM 高併發架構下,連線池碎片化導致資源浪費,**建議遷移至成熟的 HTTP 客戶端庫**。