Вам придется вызывать curl_close() два раза, прежде чем дескриптор будет закрыт, и кусок cookie может быть прочитан. Это ошибка?

Я несколько часов стучал головой о стену, пытаясь понять, почему файл cookie cookie cURL был пуст, когда я пытался его прочитать. Я только что обнаружил, что мой код работает, если я вызываю curl_close() дважды, а не один раз, и мне интересно, является ли это ошибкой cURL.

Вот пример:

curl_close($chInfo['handle']);
var_dump(is_resource($chInfo['handle']));

Это выводит boolean true. Другими словами, дескриптор не закрыт, несмотря на то, что я назвал curl_close().

Моя следующая мысль заключалась в том, что, возможно, для закрытия дескриптора требуется некоторое время, поэтому я попытался использовать sleep() в течение нескольких секунд после вызова curl_close(), но не было никакой разницы.

Из отчаяния я попытался скопировать строку curl_close(), например:

curl_close($chInfo['handle']);
curl_close($chInfo['handle']);
var_dump(is_resource($chInfo['handle']));

Это выводит boolean false, что означает, что дескриптор закрыт, и я могу читать из файла jar файла cookie (cURL записывает файлы cookie в файл, когда дескриптор закрыт).

Итак, что здесь происходит? Это кажется ужасным, как ошибка!

EDIT: я не могу опубликовать свой полный код (вы все равно не хотели бы его читать!), но вот упрощенный пример (обратите внимание, что в этом примере извлекается только один URL-адрес, тогда как в моем реальном коде curl_multi используется для извлечения многих URL одновременно):

$curlOptions = array(
    CURLOPT_USERAGENT      => 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101',
    CURLOPT_CONNECTTIMEOUT => 5, // the number of seconds to wait while trying to connect.
    CURLOPT_TIMEOUT        => 5, // the maximum number of seconds to allow cURL functions to execute.
    CURLOPT_RETURNTRANSFER => 1, // TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.
    CURLOPT_FOLLOWLOCATION => 1,
    CURLOPT_MAXREDIRS      => 10,
    CURLOPT_AUTOREFERER    => 1,
    CURLOPT_REFERER        => null,
    CURLOPT_POST           => 0,  // GET request by default
    CURLOPT_POSTFIELDS     => '', // no POST data by default
    CURLINFO_HEADER_OUT    => 1, // allows the request header to be retrieved
    CURLOPT_HEADER         => 1, // returns the response header along with the page body
    CURLOPT_URL            => 'http://www.example.com/',
    CURLOPT_COOKIEJAR      => __DIR__ . '/cookie.txt',
    CURLOPT_COOKIEFILE     => __DIR__ . '/cookie.txt'
);


$ch = curl_init();
curl_setopt_array($ch, $curlOptions); // set the options for this handle

$mh = curl_multi_init();
$responses = array();
curl_multi_add_handle($mh, $ch); // add the handle to the curl_multi object

do
{
    $result   = curl_multi_exec($mh, $running);
    $activity = curl_multi_select($mh);    // blocks until there activity on the curl_multi connection (in which case it returns a number > 0), or until 1 sec has passed

    while($chInfo = curl_multi_info_read($mh))
    {
        $chStatus = curl_getinfo($chInfo['handle']);

        if($chStatus['http_code'] == 200) // if the page was retrieved successfully
        {
            $response = curl_multi_getcontent($chInfo['handle']); // get the response

            curl_multi_remove_handle($mh, $chInfo['handle']); // remove the curl handle that was just completed
            curl_close($chInfo['handle']);                    // close the curl handle that was just completed (cookies are saved when the handle is closed?)
            curl_close($chInfo['handle']);

            var_dump(is_resource($chInfo['handle']));
        }
        else // request failed
        {
            echo 'Error: Request failed with http_code: ' . $chStatus['http_code'] . ', curl error: ' . curl_error($chInfo['handle']). PHP_EOL;
        }
    }
} while ($running > 0);

curl_multi_close($mh);

Если вы запустите указанный выше код, выход будет

boolean false

Указание на то, что ручка закрыта. Однако, если вы удалите второй вызов curl_close(), то выход изменится на

boolean true

Указание на то, что ручка не закрыта.

Ответ 1

Это не ошибка, а то, как она работает. Если вы посмотрите на исходный код, вы увидите, что происходит.

Сначала вы открываете дескриптор с $ch = curl_init(); и, смотря на источник в ext\curl\interface.c, вы можете видеть, что внутри он устанавливает ch->uses = 0;

Затем вы вызываете curl_multi_add_handle($mh, $ch); и смотрите ext\curl\multi.c, этот метод делает ch->uses++;. В этот момент ch->uses==1

Теперь, когда последняя часть, глядя на curl_close($chInfo['handle']);, снова в ext\curl\interface.c, имеет следующий код:

if (ch->uses) {
    ch->uses--;
} else {
    zend_list_delete(Z_LVAL_P(zid));
}

Итак, первая попытка закрыть его уменьшит ch->uses, а вторая попытка - фактически закрыть его.

Этот внутренний указатель увеличивается только при использовании curl_multi_add_handle или при использовании curl_copy_handle. Поэтому я предполагаю, что идея curl_multi_add_handle заключалась в том, чтобы использовать копию дескриптора, а не фактический дескриптор.

Ответ 2

Здесь нет проблемы. При использовании multi-curl вам не нужно вызывать curl_close. Вместо этого вы должны вызвать curl_multi_remove_handle для каждого используемого дескриптора. Таким образом, вызов curl_close в вашем коде является избыточным.

См. примеры правильного потока multi-curl здесь: 1, 2.

Ответ 3

"Ручка" не замкнута в цикле после цикла вы можете удалить дескрипторы

    curl_multi_remove_handle($mh, $ch1);
    /* this is not suppose to be required but the remove sometimes fails to close the connection */
    curl_close($ch1); 
    curl_multi_remove_handle($mh, $ch2);
    curl_close($ch2);

if you set up your connections as an array you can remove them through a separate loop after the main loop.

    /* init and add connection */
    foreach ($multi_urls as $i => $url) 
    {
        $ch[$i] = curl_init($url);
        curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1);
        curl_multi_add_handle ($mh, $ch[$i]);
    }

    main loop {
        ....
    }

    /* remove and close connection */
    foreach($ch AS $i => $conn)
    { 
       curl_multi_remove_handle($mh, $ch[$i]);
       curl_close($ch[$i]);
    }

Ответ 4

Я думаю, что есть только 1 ошибка после просмотра кода i.e.

while($chInfo = curl_multi_info_read($mh))

изменить с помощью

while($chInfo == curl_multi_info_read($mh))