在PHP 中使用cURL 擴展進行並發HTTP 請求時, curl_multi_*函數提供了很好的支持。特別是curl_multi_close和curl_getinfo這兩個函數,經常需要一起使用來處理多個請求的響應。然而,在實際開發中,開發者可能會遇到一些坑,尤其是在配合使用curl_getinfo獲取每個請求的響應信息時。如果不小心,可能會導致意想不到的錯誤或者獲取不到正確的信息。
curl_multi_close用來關閉多個cURL 會話句柄,在請求完成之後,你需要調用該函數來釋放所有的資源。如果你在關閉會話之前使用了curl_getinfo獲取某個請求的詳細信息,可能會遇到一個問題:關閉會話後,這些會話句柄所關聯的資源就被銷毀了,你再去調用curl_getinfo就無法獲取正確的響應信息。
// 示例代碼:錯誤的做法
$mh = curl_multi_init();
$ch1 = curl_init("https://gitbox.net/api/v1/resource1");
$ch2 = curl_init("https://gitbox.net/api/v1/resource2");
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
do {
$mrc = curl_multi_exec($mh, $active);
} while ($active);
// 錯誤:在關閉會話句柄之前調用 curl_getinfo
$info1 = curl_getinfo($ch1); // 這裡會出錯,因為會話已經關閉
$info2 = curl_getinfo($ch2);
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);
在上面的例子中,我們在curl_multi_close之前調用了curl_getinfo ,這時返回的信息已經不可用,甚至可能會導致PHP 錯誤。
為了避免上述問題,你應該在調用curl_multi_close之前,先使用curl_getinfo獲取每個請求的響應信息。以下是修改後的正確示範:
// 正確做法
$mh = curl_multi_init();
$ch1 = curl_init("https://gitbox.net/api/v1/resource1");
$ch2 = curl_init("https://gitbox.net/api/v1/resource2");
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
do {
$mrc = curl_multi_exec($mh, $active);
} while ($active);
// 正確:在關閉會話之前獲取響應信息
$info1 = curl_getinfo($ch1);
$info2 = curl_getinfo($ch2);
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);
// 現在你可以安全地使用 $info1 和 $info2 了
在這個修改後的版本中,我們確保在關閉所有會話句柄之前,先通過curl_getinfo獲取了每個請求的響應信息。這樣可以避免在關閉會話句柄後嘗試訪問已經釋放的資源,導致錯誤發生。
如果你同時發送多個請求,並且希望對每個請求獲取響應信息,通常會使用一個循環。這個時候,你需要確保每個請求的curl_getinfo都是在curl_multi_close之前獲取的,否則會發生資源丟失。
// 正確的做法:使用循環逐個獲取每個請求的信息
$mh = curl_multi_init();
$channels = [];
$urls = [
"https://gitbox.net/api/v1/resource1",
"https://gitbox.net/api/v1/resource2"
];
foreach ($urls as $url) {
$ch = curl_init($url);
curl_multi_add_handle($mh, $ch);
$channels[] = $ch;
}
do {
$mrc = curl_multi_exec($mh, $active);
} while ($active);
foreach ($channels as $ch) {
$info = curl_getinfo($ch); // 在這裡逐個獲取信息
// 處理 $info
}
foreach ($channels as $ch) {
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
在這個示例中,我們使用了一個數組來存儲每個cURL 會話句柄,並且在curl_multi_close之前逐個獲取每個請求的響應信息。這種做法避免了之前提到的問題。
有時候,可能並不是所有請求都會在同一個時間完成。為了避免對未完成請求調用curl_getinfo ,你可以使用curl_multi_select函數來檢測哪些請求已經完成,確保你只對已完成的請求調用curl_getinfo 。
// 更複雜的情況:使用 curl_multi_select 處理完成的请求
$mh = curl_multi_init();
$channels = [];
$urls = [
"https://gitbox.net/api/v1/resource1",
"https://gitbox.net/api/v1/resource2"
];
foreach ($urls as $url) {
$ch = curl_init($url);
curl_multi_add_handle($mh, $ch);
$channels[] = $ch;
}
do {
$mrc = curl_multi_exec($mh, $active);
if ($active) {
curl_multi_select($mh); // 等待某個請求完成
}
} while ($active);
foreach ($channels as $ch) {
$info = curl_getinfo($ch); // 在這裡獲取已完成請求的信息
// 處理 $info
}
foreach ($channels as $ch) {
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
使用curl_multi_select能夠確保在請求完成時及時獲取信息,從而避免因等待或超時問題而導致的數據丟失。
在PHP 中使用curl_multi_*函數時, curl_getinfo和curl_multi_close是常用的工具。避免常見坑的關鍵是:在關閉多會話之前,確保每個會話的響應信息已經通過curl_getinfo獲取。通過合適的代碼結構和請求管理,你可以確保程序的穩定性和正確性。