使用CURL Multi PHP存在不一致性问题

7
当我检查10个url时,如果能够与主机服务器建立连接,则句柄将返回成功消息(CURLE_OK)。
在处理每个句柄时,如果服务器拒绝连接,则句柄将包含错误消息。
问题是,我认为当我们得到一个坏的句柄时,CURL会标记这个句柄但继续处理未处理的句柄,然而事实并非如此。当我们遇到坏的句柄时,CURL会将该句柄标记为坏的,但不会处理其余未处理的句柄。
如果我与所有句柄都建立连接,这种情况很难被发现,因为大多数情况下都是这样的(CURL仅在第一个坏连接上停止)。
为了测试,我必须找到一个适合加载缓慢/拒绝x数量的同时连接的网站。
set_time_limit(0);

$l = array(
    'http://smotri.com/video/list/',
    'http://smotri.com/video/list/sports/',
    'http://smotri.com/video/list/animals/',
    'http://smotri.com/video/list/travel/',
    'http://smotri.com/video/list/hobby/',
    'http://smotri.com/video/list/gaming/',
    'http://smotri.com/video/list/mult/',
    'http://smotri.com/video/list/erotic/',
    'http://smotri.com/video/list/auto/',
    'http://smotri.com/video/list/humour/',
    'http://smotri.com/video/list/film/'
);


$mh = curl_multi_init();

$s = 0;
$f = 10;

while($s <= $f)
{   

    $ch = curl_init();  

    $curlsettings = array(
        CURLOPT_URL => $l[$s],
        CURLOPT_TIMEOUT => 0,
        CURLOPT_CONNECTTIMEOUT => 0,
        CURLOPT_RETURNTRANSFER => 1
    );

    curl_setopt_array($ch, $curlsettings);
    curl_multi_add_handle($mh,$ch);

    $s++;

    }

$active = null;

do 
{
    curl_multi_exec($mh,$active);
    curl_multi_select($mh);

    $info = curl_multi_info_read($mh);

    echo '<pre>';
    var_dump($info);

    if($info['result'] === CURLE_OK)
        echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' success<br>';

    if($info['result'] != 0)
        echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' failed<br>';

} while ($active > 0);

curl_multi_close($mh);

我已经在脚本中转储了$info,询问Multi Handle是否有任何正在处理的句柄上有任何新信息。

当脚本结束时,如果没有新信息可用(句柄仍在处理),我们将看到一些bool(false),并且如果所有操作都成功,则会返回所有句柄,如果一个句柄失败则仅返回有限的句柄。

我无法修复这个问题,可能是我忽视了什么,而且我已经尝试修复一些与此无关的东西。

一些尝试修复的方法如下:

  • 将每个$ch句柄分配给数组-$ch[1]、$ch[2]等(而不是向multi_handle添加当前$ch句柄,然后覆盖-如测试中所述)

  • 成功/失败后删除处理程序

  • 将CURLOPT_CONNECTTIMEOUT和CURLOPT_TIMEOUT设置为无限大。

    • 还有很多。(我会在更新帖子时补充)

使用Php版本5.4.14进行测试,希望我已经足够清楚地说明了这些问题。

感谢您的阅读。


这并不是你问题的答案,因为我不确定在你的情况下到底发生了什么,但是我有一个REST客户端库,利用了curl_multi_exec()。 我从来没有看到过这样的问题(我已经使用它每天处理数百万个REST调用)。 你可以随意使用它或查看内部代码以获取灵感。 https://github.com/mikecbrant/php-rest-client/blob/master/rest_client.class.php - Mike Brant
你能告诉我们你正在使用哪个版本的PHP吗?当检查结果状态时,你的代码有一个错误,CURLE_OK是一个预定义常量,而不是一个字符串。因此,你可能需要更改为 if (CURLE_OK === $info['result']) then { echo 'success'; } else { echo 'failure';} - Max
@Max,我正在运行php 5.4.14,我将使用两个版本+常量检查更新原始文件。谢谢。 - cecilli0n
1个回答

9
我已经一段时间在尝试使用你的脚本,以使其正常工作。
直到我阅读了http://se2.php.net/manual/en/function.curl-multi-info-read.php中的Repeated calls to this function will return a new result each time, until a FALSE is returned as a signal that there is no more to get at this point.,我才意识到 while 循环可能会起作用。
额外的 while 循环使它的行为与您期望的完全相同。这是我得到的输出:
http://smotri.com/video/list/sports/ failed

http://smotri.com/video/list/travel/ failed

http://smotri.com/video/list/gaming/ failed

http://smotri.com/video/list/erotic/ failed

http://smotri.com/video/list/humour/ failed

http://smotri.com/video/list/animals/ success

http://smotri.com/video/list/film/ success

http://smotri.com/video/list/auto/ success

http://smotri.com/video/list/ failed

http://smotri.com/video/list/hobby/ failed

http://smotri.com/video/list/mult/ failed


这是我用于测试的代码:

<?php
set_time_limit(0);

$l = array(
    'http://smotri.com/video/list/',
    'http://smotri.com/video/list/sports/',
    'http://smotri.com/video/list/animals/',
    'http://smotri.com/video/list/travel/',
    'http://smotri.com/video/list/hobby/',
    'http://smotri.com/video/list/gaming/',
    'http://smotri.com/video/list/mult/',
    'http://smotri.com/video/list/erotic/',
    'http://smotri.com/video/list/auto/',
    'http://smotri.com/video/list/humour/',
    'http://smotri.com/video/list/film/'
);

$mh = curl_multi_init();

$s = 0;
$f = 10;

while($s <= $f)
{   
    $ch = curl_init();  

    if($s%2)
    {
        $curlsettings = array(
            CURLOPT_URL => $l[$s],
            CURLOPT_TIMEOUT_MS => 3000,
            CURLOPT_RETURNTRANSFER => 1,
        );
    }
    else
    {
        $curlsettings = array(
            CURLOPT_URL => $l[$s],
            CURLOPT_TIMEOUT_MS => 4000,
            CURLOPT_RETURNTRANSFER => 1,
        );
    }

    curl_setopt_array($ch, $curlsettings);
    curl_multi_add_handle($mh,$ch);
    $s++;
}

$active = null;

do 
{

    $mrc = curl_multi_exec($mh,$active);
    curl_multi_select($mh);

    while($info = curl_multi_info_read($mh))
    {
        echo '<pre>';
        //var_dump($info);

        if($info['result'] === 0)
        {
            echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' success<br>';
        }
        else
        {
            echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' failed<br>';
        }   
    }

} while ($active > 0);

curl_multi_close($mh);

希望能够帮到你。对于测试,只需将CURLOPT_TIMEOUT_MS调整为您的网络连接即可。我将它设置为在3000和4000毫秒之间交替,因为3000会失败,而4000通常会成功。
更新
经过查阅PHP和libCurl文档,我发现了curl_multi_exec的工作原理(在libCurl中是curl_multi_perform)。第一次被调用时,它开始处理所有已添加的句柄的传输(通过curl_multi_add_handle先添加)。
它分配给$active的数字是仍在运行的传输数量。因此,如果它小于您拥有的句柄的总数,那么您就知道一个或多个传输已完成。因此,curl_multi_exec也充当了一种进度指示器。
由于所有传输都以非阻塞方式处理(传输可以同时完成),所以while循环中的curl_multi_exec无法表示每个完成的URL请求的迭代。
所有数据都存储在队列中,因此一旦一个或多个传输完成,您就可以调用curl_multi_info_read来获取这些数据。
在我的原始回答中,我使用了curl_multi_info_read并将其放在while循环中。此循环将继续迭代,直到curl_multi_info_read在队列中找不到剩余数据为止。之后,如果$active!=0(表示curl_multi_exec报告的传输仍未完成),外部while循环将进入下一次迭代。
总之,当仍有未完成的传输时,外部循环将继续迭代,而内部循环仅在来自已完成传输的数据时进行迭代。
PHP文档对于curl多功能函数非常糟糕,因此我希望这可以澄清一些事情。下面是执行相同操作的另一种替代方法。
do 
{
    curl_multi_exec($mh,$active);
} while ($active > 0);

// while($info = curl_multi_info_read($mh)) would work also here
for($i = 0; $i <= $f; $i++){
    $info = curl_multi_info_read($mh);

    if($info['result'] === 0)
    {
        echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' success<br>';
    }
    else
    {
        echo curl_getinfo($info['handle'],CURLINFO_EFFECTIVE_URL) . ' failed<br>';
    }
}

从这些信息中,您还可以看到不需要curl_multi_select,因为您不希望出现阻塞等待活动的情况。
您在问题中提供的代码似乎只是在几次失败的传输后停顿了,但实际上缓冲区中仍然有数据排队。您的代码只是没有足够多次地调用curl_multi_info_read。所有成功的传输都被您的代码捕获,原因是PHP在单个线程上运行,因此脚本挂起等待请求。失败请求的超时并没有对PHP产生足够的影响,以使其挂起/等待那么长时间,因此while循环执行的迭代次数少于排队数据的数量。

现在去测试一下,测试的好主意,我会编辑我的原帖来包含这个。 - cecilli0n
你的代码是可以工作的,但是为了测试,我决定删除你额外添加的 while 循环(实际上将脚本恢复到我的旧脚本),出乎意料的是这个脚本也能正常工作。我进一步研究了可能的原因,发现当你指定 CURLOPT_TIMEOUT_MS (4000) 时,它允许我的脚本在任何失败的获取后继续运行。然后我将 CURLOPT_TIMEOUT_MS 改回 0(无限制),一旦我这样做,我的脚本就会在第一个失败的句柄处停止处理。 然后我重新添加了你建议的 while 循环,同时将 CURLOPT_TIMEOUT_MS 设置为 0,期望可能会有失败。 - cecilli0n
继续。但实际上即使出现故障,它仍然继续处理(有效地做到了我需要的)。 说了这么多,你知道是什么导致我的旧脚本失败了吗?我不太明白为什么它会在失败时停止。 - cecilli0n
唯一的关键区别就是额外的 while 循环。虽然我的答案解释了为什么需要这样做,但并没有完全解释为什么 curl 在您的原始代码中在失败后不能继续执行而在成功后可以继续执行。话虽如此,在加入额外的 while 循环之前,我确实注意到(脚本无法执行时)有时候脚本实际上会继续执行几乎所有的 url。大多数情况下,该脚本会输出1-3个错误。这可能是一个关于 curl 的问题,因为它自己编码为 C 的原因也可能涉及其中。 - James T
下班后我会仔细看看,看看我能找到什么。 - James T
1
我更新了我的答案@cecilli0n。 - James T

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接