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 实例;以前,期望一个 resource

范例

范例 #1 curl_multi_exec() 范例

此示例将创建两个 cURL 句柄,将它们添加到多句柄中,并异步处理它们。

<?php
// 创建两个 cURL 资源
$ch1 = curl_init();
$ch2 = curl_init();

// 设置 URL 和其他适当的选项
curl_setopt($ch1, CURLOPT_URL, "http://example.com/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "https://php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

// 创建多个 cURL 句柄
$mh = curl_multi_init();

// 添加两个句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

// 执行多句柄
do {
$status = curl_multi_exec($mh, $active);
if (
$active) {
// 等待一段时间以获得更多活动
curl_multi_select($mh);
}
} while (
$active && $status == CURLM_OK);

// 关闭句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

?>

参见

添加笔记

用户贡献笔记 17 笔记

69
Ren
10 年前
解决 CPU 100% 使用率,更简单更正确的方法

<?php

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

?>
27
Zappie
8 年前
您可能还想能够将 HTML 内容下载到缓冲区/变量中,以便解析 HTML 或在您的程序中进行其他处理。

此页面上的示例代码只输出所有内容到屏幕上,而没有给你将下载的页面保存到字符串变量中的可能性。因为我想做的是下载多个页面(并不奇怪,对吧?这就是使用多页并行 Curl 的原因),所以我最初很困惑,因为这个页面没有提供关于如何做到这一点的指南。

幸运的是,有一种方法可以并行 Curl 请求下载内容(就像您对单个下载使用常规 curl_exec 一样)。您需要使用:https://php.net/manual/en/function.curl-multi-getcontent.php

curl_multi_getcontent 函数绝对应该在 curl_multi_exec 的“参见”部分中提及。可能大多数来到 curl_multi_exec 文档页面的人,实际上想要将多个 HTML 页面(或来自多个并行 Curl 连接的其他内容)下载到缓冲区中,每个页面一个缓冲区。
23
Silvio Garbes
8 年前
// 所有 url 都记录在数组中
$url[] = 'http://www.link1.com.br';
$url[] = 'https://www.link2.com.br';
$url[] = 'https://www.link3.com.br';

// 为所有 url 设置默认选项并将它们添加到队列中以进行处理
$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);
6
gmail at com dot asmqb7
5 年前
/!\ 注意
/!\ 此页面上许多未被否定投票的笔记都使用了过时的信息。

CURLM_CALL_MULTI_PERFORM 返回代码自 2012 年左右(至少 7 年前)就已失效。

引用 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 <-- 可能是我在这里能提供的最具前瞻性的有用链接
3
Timo Huovinen
4 年前
仅仅是为了帮助那些难以让它工作的人,这里是我的方法。
没有无限循环,没有 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.net",
CURLOPT_HEADER=>false,
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>false,

// https://stackoverflow.com/a/41135574
CURLOPT_HEADERFUNCTION=>function($ch, $header)
{
print
"header from https://php.net: ".$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);
?>
1
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;

// 一半的 URL 将在 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);
});

// 在 Fiber 处于挂起状态时,在后台运行 curl multi exec
$fiber->start($mh_fiber);

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

do {
/**
* 此时 Fiber 中的 curl 可能已经完成了
* 因此我们需要在 Fiber 中使用另一个 "do while" 循环来刷新 $still_running 变量
**/
$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);
?>
-1
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;
// 执行句柄
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>select error</h1>";
break;
}
} while (
true);
?>

这个在没有运行非阻塞循环,浪费CPU时间的情况下运行良好 (PHP 5.2.5 @ FBSD 6.2)。

但是这似乎是 curl_multi_select 的唯一用途,因为没有简单的方法将其与其他 PHP select 系统调用的包装器绑定。
-3
jorov at mail dot bg
9 年前
我尝试了 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选项添加到每个句柄
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();

// 添加句柄
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] ); // 获取结果

// 关闭当前句柄
curl_multi_remove_handle($mh, $handles[$k] );
}
curl_multi_close($mh);
return $res; // 返回响应
}
-9
pulksh at gmail dot com
10 年前
此示例在 Windows 7 上与 PHP 版本 5.3.27 不兼容。
-8
qdujunjie at 126 dot com
9 年前
在 OS X Mavericks 系统上运行 curl_multi_exec() 时,您可能无法获得正确的结果,
脚本可能一直运行但没有返回值,
这是因为在 OS X 系统上,PHP 版本 5.3.8+,在调用 curl_multi_exec() 方法之前调用 curl_multi_select(),

可能会一直返回 -1。
-17
因此,这里需要稍微修改一下代码,以便在 OS X 上成功运行 curl_multi_exec():
9 年前
<?php

$ch1
= curl_init();
$ch2 = curl_init();

// 设置URL和相应的选项
curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "https://php.net");
curl_setopt($ch2, CURLOPT_HEADER, 0);

// 创建批处理cURL句柄
$mh = curl_multi_init();

// 增加2个句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

$active = null;

do {
$mrc = curl_multi_exec($mh, $active);
} while (
$mrc == CURLM_CALL_MULTI_PERFORM);

while (
$active && $mrc == CURLM_OK)
{
// 添加这一行
while (curl_multi_exec($mh, $active) === CURLM_CALL_MULTI_PERFORM);

if (
curl_multi_select($mh) != -1)
{
do {
$mrc = curl_multi_exec($mh, $active);
if (
$mrc == CURLM_OK)
{
while(
$info = curl_multi_info_read($mh))
{
var_dump($info);
}
}
} while (
$mrc == CURLM_CALL_MULTI_PERFORM);
}
}

// 关闭句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

?>
qdujunjie at 126 dot com
当在 OS X Mavericks 中运行 curl_multi_exec() 时,
可能会得不到想要的结果,
因为脚本会一直执行但是却没有返回值,
这是因为在 OS X 中当 PHP 版本在 5.3.8 以上版本时,
在调用 curl_multi_exec() 方法之前调用 curl_multi_select() 会一直返回 -1,

可能会一直返回 -1。
-6
请确保在尝试再次读取文件之前关闭用于连接的原始文件句柄,即使你使用新的文件句柄来操作也是如此。如果你不这样做,
file_get_contents() 或 fread() 会截断你的文件,只返回有限的大小,在我的例子中是 40960 个字符,没有其他解释或错误信息。文件会存在于你的磁盘上(并且是完整的),
只是 PHP 不会显示它。

我(也许错误地)认为这是一个错误,所以我创建了一个错误报告,请参阅它以了解我用来重现这种奇怪行为的代码示例:

https://bugs.php.net/bug.php?id=62409
Jimmy Ruska

15 年前
-4
> 回复 viczbk.ru
分享一下我的尝试:
> while (($res = curl_multi_select($mh)) === 0) {};

这在我的 Windows 计算机 (php 5.2.5) 上运行良好,但当我将 curl 程序运行在我的新的 CentOS 服务器 (php 5.1.6) 上时,

该函数除非添加 curl_multi_exec() 到循环中才更新,而这消除了使用它来节省周期的目的。curl_multi_select() 也可以设置超时时间,

但这似乎没有帮助,再说一遍,我不明白为什么人们不使用它。

do {
即使 curl_multi_select($mh) 工作,也没有办法知道哪些套接字更新了状态,或者它们是否收到了部分数据,
完全完成,还是仅仅超时。如果你想在事情完成时立即删除/添加数据,它不可靠。尝试只调用 example.com 或其他数据量很少的网站。
curl_multi_select($mh) 可能在完成之前发送数千次非 0 值。惰性地添加 usleep(25000) 或一些最小值也可以帮助不浪费周期。
evaisse at gmail dot com
6 年前
}
此示例在 PHP5.6 上并不完全有效。

使用 curl_multi 和 CURLOPT_VERBOSE & CURLOPT_STDERR,我发现如果在任何“经典”cURL 请求之前发送 curl_multi 请求,
-9
将会导致对某些主机名的超时错误。错误消息显示为:
> * 主机名在 DNS 缓存中未找到
似乎在第一个循环中添加这行代码可以解决这个问题:
-24
$mrc = curl_multi_exec($mh, $active);
可以在 OS X 中成功执行:
// 检查错误
if ($mrc > 0) {

<?php
// 作者:Daniel G Zylberberg
// 日期:2012 年 7 月 11 日
// $res:数组结构 0=>array("url"=>"blah"),1=>array("url"=>"some url")
// $options(可选):包含 curl 选项(超时、postfields 等)的数组
// 返回与获取的数组相同,并在当前行(html 内容)中添加 "data"
// 以及 "error",如果失败,则包含字符串描述。

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

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

$handles = array();

if(!
$options) // 添加默认选项
$options = array(
CURLOPT_HEADER=>0,
CURLOPT_RETURNTRANSFER=>1,
);

// 将 curl 选项添加到每个句柄
foreach($res as $k=>$row){
$ch{$k} = curl_init();
$options[CURLOPT_URL] = $row['url'];
curl_setopt_array($ch{$k}, $options);
$handles[$k] = $ch{$k};
}

$mh = curl_multi_init();

foreach(
$handles as $k => $handle){
curl_multi_add_handle($mh,$handle);
//echo "<br>adding handle {$k}";
}

$running_handles = null;
// 执行句柄
do {
$status_cme = curl_multi_exec($mh, $running_handles);
} while (
$cme == CURLM_CALL_MULTI_PERFORM);

while (
$running_handles && $status_cme == CURLM_OK) {
if (
curl_multi_select($mh) != -1) {
do {
$status_cme = curl_multi_exec($mh, $running_handles);
// echo "<br>''threads'' running = {$running_handles}";
} while ($status == CURLM_CALL_MULTI_PERFORM);
}
}

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] ); // 获取结果

// 关闭当前句柄
curl_multi_remove_handle($mh, $handles[$k] );
}
curl_multi_close($mh);
return
$res; // 返回响应
}

$res = array(
"11"=>array("url"=>"http://localhost/public_html/test/sleep.php?t=1"),
"13"=>array("url"=>"http://localhost/public_html/test/sleep.php?t=3"),
"25"=>array("url"=>"this doesn't exist"),

);
print_r( multiCurl($res));
?>

---------- sleep.php -------------------------------------
<?php
sleep
($_GET['t']);
echo
"sleep for {$_GET['t']} seconds and show this.";
?>
-13
robert dot reichel at boboton dot com
16 年前
我在测试 PHP 手册中 curl_multi_exec 部分提供的由 [email protected] 提供的 PHP 代码。

代码中的 “$err = curl_error($conn[$i])” 部分应该返回每个 cURL 会话的错误消息,但它没有。
curl_error() 函数与 curl_exec() 一起使用时效果很好。有没有其他解决方案可以获取使用 curl_multi_exec() 的会话错误消息,或者 cURL 库中存在错误?

该脚本已在 Windows XP 和 PHP-5.2.4 上测试。
To Top