curl_multi 系列函數實現**單進程多並發 HTTP 請求**,但官方文檔極其簡略,網上流傳的「簡單示例」存在**死循環佔滿 CPU** 的嚴重缺陷。本文從原理到生產級代碼完整演進。
curl_multi_init() 創建批處理句柄 $mh。curl_init($url) 創建子句柄 → curl_setopt() 設置選項 → curl_multi_add_handle($mh, $ch) 加入批處理。curl_multi_exec($mh, $active) 驅動傳輸。curl_multi_getcontent($ch) 獲取結果。curl_multi_remove_handle($mh, $ch) 移除子句柄 → curl_close($ch) 釋放資源。curl_multi_close($mh) 關閉批處理句柄。do {
$n = curl_multi_exec($mh, $active);
} while ($active); // ❌ 無數據時空轉,CPU 100%
// 1. 執行所有可立即執行的動作
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
// 2. 有活動句柄且狀態 OK 時,進入事件循環
while ($active && $mrc == CURLM_OK) {
// 關鍵:select 等待任意句柄就緒(可讀/可寫/異常),超時返回 -1
if (curl_multi_select($mh) != -1) {
// 有新數據到達,繼續執行
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
// select 返回 -1 表示無文件描述符就緒(極少見),或超時繼續循環
}
curl_multi_select($mh) 封裝了 select() 系統調用,**阻塞等待網絡 I/O 就緒**,釋放 CPU。>=0 就緒描述符數、-1 錯誤/無描述符、0 超時(默認 1 秒)。CURLM_CALL_MULTI_PERFORM 表示「還有內部工作可立即做」,需緊湊循環調用 curl_multi_exec 直到狀態變為 CURLM_OK。function curl_multi_request(array $urls, array $options = [], $timeout = 10)
{
$mh = curl_multi_init();
$chs = [];
foreach ($urls as $key => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, array_merge([
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境
], $options));
curl_multi_add_handle($mh, $ch);
$chs[$key] = $ch;
}
$active = null;
do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
$results = [];
foreach ($chs as $key => $ch) {
$error = curl_error($ch);
$results[$key] = $error ? ['error' => $error] : curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
// 使用示例
$results = curl_multi_request([
'https://www.baidu.com/',
'https://www.google.com/',
'https://www.jb51.net/'
]);
print_r($results);
CURLOPT_TIMEOUT(總超時)、CURLOPT_CONNECTTIMEOUT(連接超時)。curl_error($ch) 非空即失敗,curl_getinfo($ch, CURLINFO_HTTP_CODE) 獲取 HTTP 狀態碼。ulimit -n)。CurlHandle 物件化 API、amphp/http-client、Guzzle 異步客戶端、Swoole 協程 HTTP 客戶端(高性能場景首選)。