PHP Conference Japan 2024

curl_multi_exec

(PHP 5, PHP 7, PHP 8)

curl_multi_exec執行目前 cURL 句柄的子連線

說明

curl_multi_exec(CurlMultiHandle $multi_handle, int &$still_running): int

處理堆疊中的每個句柄。無論句柄是否需要讀取或寫入資料,都可以呼叫此方法。

參數

multi_handle

curl_multi_init() 返回的 cURL 多重句柄。

still_running

用於指示操作是否仍在執行的旗標的參考。

回傳值

在 cURL 預定義常數 中定義的 cURL 代碼。

注意事項:

這個函數只會返回有關整個多重堆疊的錯誤。即使此函數返回 CURLM_OK,個別的傳輸仍然可能發生問題。

更新日誌

版本 說明
8.0.0 multi_handle 現在預期是一個 CurlMultiHandle 實例;以前預期是一個 資源

範例

範例 #1 curl_multi_exec() 範例

這個範例將為 URL 列表建立 curl 控制代碼,將它們添加到多重控制代碼,並非同步處理它們。

<?php

$urls
= [
"https://php.dev.org.tw/",
"https://www.example.com/",
];

$mh = curl_multi_init();
$map = new WeakMap();

foreach (
$urls as $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_multi_add_handle($mh, $ch);
$map[$ch] = $url;
}

do {
$status = curl_multi_exec($mh, $unfinishedHandles);
if (
$status !== CURLM_OK) {
throw new
\Exception(curl_multi_strerror(curl_multi_errno($mh)));
}

while ((
$info = curl_multi_info_read($mh)) !== false) {
if (
$info['msg'] === CURLMSG_DONE) {
$handle = $info['handle'];
curl_multi_remove_handle($mh, $handle);
$url = $map[$handle];

if (
$info['result'] === CURLE_OK) {
$statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);

echo
"Request to {$url} finished with HTTP status {$statusCode}:", PHP_EOL;
echo
curl_multi_getcontent($handle);
echo
PHP_EOL, PHP_EOL;
} else {
echo
"Request to {$url} failed with error: ", PHP_EOL;
echo
curl_strerror($info['result']);
echo
PHP_EOL, PHP_EOL;
}
}
}

if (
$unfinishedHandles) {
if ((
$updatedHandles = curl_multi_select($mh)) === -1) {
throw new
\Exception(curl_multi_strerror(curl_multi_errno($mh)));
}
}
} while (
$unfinishedHandles);

curl_multi_close($mh);

?>

另請參閱

新增筆記

使用者貢獻的筆記 9 則筆記

Ren
11 年前
解決 CPU 100% 使用率,一個更簡單且正確的方法

<?php

do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while (
$running > 0);

?>
Zappie
9 年前
您可能還希望能夠將 HTML 內容下載到緩衝區/變數中,以便解析 HTML 或在您的程式中進行其他處理。

此頁面上的範例程式碼僅在螢幕上輸出所有內容,而沒有讓您能夠將下載的頁面儲存到字串變數中。因為下載多個頁面是我想要做的(並不意外,對吧?這就是使用多頁面平行 Curl 的原因),所以我一開始感到困惑,因為此頁面沒有提供如何做到這一點的指南。

幸運的是,有一種方法可以使用平行 Curl 請求下載內容(就像您使用普通的 curl_exec 進行單一下載一樣)。您需要使用:https://php.dev.org.tw/manual/en/function.curl-multi-getcontent.php

函數 curl_multi_getcontent 絕對應該在 curl_multi_exec 的「另請參閱」部分中提及。可能大多數找到 curl_multi_exec 文件頁面的人,實際上是想將多個 HTML 頁面(或來自多個平行 Curl 連線的其他內容)下載到緩衝區中,每個頁面一個緩衝區。
Silvio Garbes
8 年前
// 所有網址記錄在陣列中
$url[] = 'http://www.link1.com.br';
$url[] = 'https://www.link2.com.br';
$url[] = 'https://www.link3.com.br';

// 設定所有網址的預設選項並加入佇列以進行處理
$mh = curl_multi_init();
foreach($url as $key => $value){
$ch[$key] = curl_init($value);
curl_setopt($ch[$key], CURLOPT_NOBODY, true);
curl_setopt($ch[$key], CURLOPT_HEADER, true);
curl_setopt($ch[$key], CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch[$key], CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch[$key], CURLOPT_SSL_VERIFYHOST, false);

curl_multi_add_handle($mh,$ch[$key]);
}

// 執行查詢
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh);
} while ($running > 0);

// 取得所有查詢的資料並從佇列中移除
foreach(array_keys($ch) as $key){
echo curl_getinfo($ch[$key], CURLINFO_HTTP_CODE);
echo curl_getinfo($ch[$key], CURLINFO_EFFECTIVE_URL);
echo "\n";

curl_multi_remove_handle($mh, $ch[$key]);
}

// 結束
curl_multi_close($mh);
gmail at com dot asmqb7
5 年前
/!\ 注意
/!\ 此頁面上幾個沒有被負評的註記使用了過時的資訊。

CURLM_CALL_MULTI_PERFORM 返回碼自大約 2012 年以來就已經失效,至少七年前就已經失效了。

引用 curl 的作者,來自 https://curl.haxx.se/mail/lib-2012-08/0042.html:

> CURLM_CALL_MULTI_PERFORM 已被棄用,並且永遠不會被返回,如文件中所述。

> 在 libcurl 的多介面使用的第一個十年左右,我從未見過有人正確使用該功能。然而,我確實看到了許多錯誤和誤解。這讓我決定該功能並不重要或不夠好,因此自 7.20.0 起,CURLM_CALL_MULTI_PERFORM 已不復存在。

感謝 https://stackoverflow.com/q/19490837/3229684, 發現了所有這些,它建議使用以下替代的 while 迴圈

<?php
do {
$mrc = curl_multi_exec($mc, $active);
} while (
$active > 0);
?>

https://www.google.com/search?q=CURLM_CALL_MULTI_PERFORM <-- 我能放在這裡的最具前瞻性的有用連結
Timo Huovinen
5 年前
僅供正在努力讓它運作的人參考,這是我的方法。
沒有無限迴圈,沒有 CPU 100%,速度可以調整。

<?php
function curl_multi_exec_full($mh, &$still_running) {
do {
$state = curl_multi_exec($mh, $still_running);
} while (
$still_running > 0 && $state === CURLM_CALL_MULTI_PERFORM && curl_multi_select($mh, 0.1));
return
$state;
}
function
curl_multi_wait($mh, $minTime = 0.001, $maxTime = 1){
$umin = $minTime*1000000;

$start_time = microtime(true);
$num_descriptors = curl_multi_select($mh, $maxTime);
if(
$num_descriptors === -1){
usleep($umin);
}

$timespan = (microtime(true) - $start_time);
if(
$timespan < $umin){
usleep($umin - $timespan);
}
}

$handles = [
[
CURLOPT_URL=>"http://example.com/",
CURLOPT_HEADER=>false,
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>false,
],
[
CURLOPT_URL=>"https://php.dev.org.tw",
CURLOPT_HEADER=>false,
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>false,

// https://stackoverflow.com/a/41135574
CURLOPT_HEADERFUNCTION=>function($ch, $header)
{
print
"header from https://php.dev.org.tw: ".$header;
return
strlen($header);
}
]
];

$mh = curl_multi_init();

$chandles = [];
foreach(
$handles as $opts) {
$ch = curl_init();
curl_setopt_array($ch, $opts);
curl_multi_add_handle($mh, $ch);
$chandles[] = $ch;
}

$prevRunning = null;
do {
$status = curl_multi_exec_full($mh, $running);
if(
$running < $prevRunning){
while (
$read = curl_multi_info_read($mh, $msgs_in_queue)) {

$info = curl_getinfo($read['handle']);

if(
$read['result'] !== CURLE_OK){
print
"Error: ".$info['url'].PHP_EOL;
}

if(
$read['result'] === CURLE_OK){
/*
if(isset($info['redirect_url']) && trim($info['redirect_url'])!==''){

print "running redirect: ".$info['redirect_url'].PHP_EOL;
$ch3 = curl_init();
curl_setopt($ch3, CURLOPT_URL, $info['redirect_url']);
curl_setopt($ch3, CURLOPT_HEADER, 0);
curl_setopt($ch3, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch3, CURLOPT_FOLLOWLOCATION, 0);
curl_multi_add_handle($mh,$ch3);
}
*/

print_r($info);
//echo curl_multi_getcontent($read['handle']));
}
}
}

if (
$running > 0) {
curl_multi_wait($mh);
}

$prevRunning = $running;

} while (
$running > 0 && $status == CURLM_OK);
foreach(
$chandles as $ch){
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
?>
maxpanchnko at gmail dot com
2 年前
使用 Fibers 如何讓 multi_curl 速度提高兩倍的範例之一(虛擬碼)

<?php

$curlHandles
= [];
$urls = [
'https://example.com/1',
'https://example.com/2',
...
'https://example.com/1000',
];
$mh = curl_multi_init();
$mh_fiber = curl_multi_init();

$halfOfList = floor(count($urls) / 2);
foreach (
$urls as $index => $url) {
$ch = curl_init($url);
$curlHandles[] = $ch;

// half of urls will be run in background in fiber
$index > $halfOfList ? curl_multi_add_handle($mh_fiber, $ch) : curl_multi_add_handle($mh, $ch);
}

$fiber = new Fiber(function (CurlMultiHandle $mh) {
$still_running = null;
do {
curl_multi_exec($mh, $still_running);
Fiber::suspend();
} while (
$still_running);
});

// run curl multi exec in background while fiber is in suspend status
$fiber->start($mh_fiber);

$still_running = null;
do {
$status = curl_multi_exec($mh, $still_running);
} while (
$still_running);

do {
/**
* at this moment curl in fiber already finished (maybe)
* so we must refresh $still_running variable with one more cycle "do while" in fiber
**/
$status_fiber = $fiber->resume();
} while (!
$fiber->isTerminated());

foreach (
$curlHandles as $index => $ch) {
$index > $halfOfList ? curl_multi_remove_handle($mh_fiber, $ch) : curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
curl_multi_close($mh_fiber);
?>
viczbk.ru
16 年前
http://curl.haxx.se/libcurl/c/libcurl-multi.html

「當您添加了目前擁有的控制碼(您仍然可以隨時添加新的控制碼)後,您可以通過呼叫 curl_multi_perform(3) 來啟動傳輸。

curl_multi_perform(3) 是非同步的。它只會執行盡可能少的操作,然後將控制權返回給您的程式。它被設計成永遠不會阻塞。如果它返回 CURLM_CALL_MULTI_PERFORM,您最好盡快再次呼叫它,因為這是一個信號,表明它仍然有本地資料要發送或遠端資料要接收。」

因此,範例腳本中的迴圈看起來應該是這樣的

<?php
$running
=null;
//執行 handles
do {
while (
CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running));
if (!
$running) break;
while ((
$res = curl_multi_select($mh)) === 0) {};
if (
$res === false) {
echo
"<h1>選擇錯誤</h1>";
break;
}
} while (
true);
?>

這在 (PHP 5.2.5 @ FBSD 6.2) 上運作良好,無須執行非阻塞迴圈且不會浪費 CPU 時間。

然而,這似乎是 curl_multi_select 的唯一用途,因為沒有簡單的方法將它與其他用於 select 系統呼叫的 PHP 包裝器綁定。
jorov at mail dot bg
10 年前
我嘗試了 Daniel G Zylberberg 的函式,
但它並不如貼文所述般運作。
我做了一些修改讓它可以運作,以下是我使用的程式碼

function multiCurl($res, $options=""){

if(count($res)<=0) return False;

$handles = array();

if(!$options) // 加入預設選項
$options = array(
CURLOPT_HEADER=>0,
CURLOPT_RETURNTRANSFER=>1,
);

// 將 curl 選項加入每個 handle
foreach($res as $k=>$row){
$ch{$k} = curl_init();
$options[CURLOPT_URL] = $row['url'];
$opt = curl_setopt_array($ch{$k}, $options);
var_dump($opt);
$handles[$k] = $ch{$k};
}

$mh = curl_multi_init();

// 加入 handles
foreach($handles as $k => $handle){
$err = curl_multi_add_handle($mh, $handle);
}

$running_handles = null;

do {
curl_multi_exec($mh, $running_handles);
curl_multi_select($mh);
} while ($running_handles > 0);

foreach($res as $k=>$row){
$res[$k]['error'] = curl_error($handles[$k]);
if(!empty($res[$k]['error']))
$res[$k]['data'] = '';
else
$res[$k]['data'] = curl_multi_getcontent( $handles[$k] ); // 取得結果

// 關閉目前的 handle
curl_multi_remove_handle($mh, $handles[$k] );
}
curl_multi_close($mh);
return $res; // 回傳回應
}
evaisse at gmail dot com
6 年前
這個範例在 PHP5.6 上無法完全正常運作。

使用 curl_multi 和 CURLOPT_VERBOSE 以及 CURLOPT_STDERR,我發現如果在任何「傳統」cURL 請求之前發送 curl_multi 請求,將會導致針對某些主機名稱發生逾時錯誤。錯誤訊息顯示:

> * 在 DNS 快取中找不到主機名稱

將這行程式碼加入第一個迴圈似乎可以解決這個問題。

do {
$mrc = curl_multi_exec($mh, $active);
// 檢查錯誤
if ($mrc > 0) {
// 顯示錯誤
error_log(curl_multi_strerror($mrc));
}
} while ($mrc === CURLM_CALL_MULTI_PERFORM || $active);

來源:https://stackoverflow.com/questions/30935541/php-curl-error-hostname-was-not-found-in-dns-cache
To Top