[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: curl_multi_select и curl_multi_info_read не дружат
yup
Возникла у меня задача, в рамках которой нужно периодически опрашивать достаточно большое число веб-страниц, причём делать это быстро и в то же время не сильно грузя процессор, да и всю систему в целом, так как работать должно на довольно слабой машине, на которой и без того много всякого крутится.

Подобные задачи на PHP раньше решать не приходилось, поэтому полез читать документацию по curl_* (и, в частности, по curl_multi_*).

Почитал, написал код. Не работает. Помучился-поэкспериметировал сначала сам - не помогло. Поизучал Интернет, ужаснулся увиденному, но некий работающий вариант из кусочков вселенского мусора знания собрал (URL-ы здесь просто для примера, чтобы можно было скопировать код и сразу запустить, но всё остальное: "Шаг влево, шаг вправо - пуля в голову, и не обязательно серебряная"):
<?php
$targets = array(
'https://ya.ru/',
'https://google.ru',
'https://msfn.org/',
);


$MultiCURL = curl_multi_init();
if (!curl_multi_setopt($MultiCURL, CURLMOPT_MAX_TOTAL_CONNECTIONS, 5))
die('Failed when setting max connections parameter');

$options = array(
CURLOPT_FOLLOWLOCATION => true
, CURLOPT_RETURNTRANSFER => true
, CURLOPT_CONNECTTIMEOUT => 10
, CURLOPT_TIMEOUT => 15
);
if (defined('CURLSSLOPT_NATIVE_CA') && version_compare(curl_version()['version'], '7.71', '>=')) {
$options[CURLOPT_SSL_OPTIONS] = CURLSSLOPT_NATIVE_CA;
} else {
$options[CURLOPT_CAINFO] = 'cacert.pem'; // список сертификатов брать с https://curl.se/ca/cacert.pem
}

foreach ($targets as $target) {
if (($ch = curl_init()) === false)
die('Failed init for ' . $target);
if (!curl_setopt_array($ch, $options))
die('Failed setting options for' . $target);
if (!curl_setopt($ch, CURLOPT_URL, trim($target)))
die('Failed setting proxy address' . $target);
$rc = curl_multi_add_handle($MultiCURL, $ch);
if ($rc)
die($curl_multi_status[$rc] . ' when adding ' . $target);
}

while (curl_multi_exec($MultiCURL, $running) == CURLM_CALL_MULTI_PERFORM);

while ($running > 0) {
$sel = curl_multi_select($MultiCURL, 10);

while (curl_multi_exec($MultiCURL, $running) == CURLM_CALL_MULTI_PERFORM);

if ($sel < 1) continue;

while (($info = curl_multi_info_read($MultiCURL)) != false) {
$ch = $info['handle'];
$info = curl_getinfo($ch);
$httpCode = $info['http_code'];
$text = "\r\nURL: ${info['url']} | HTTP code: $httpCode";
$text .= "\r\nTotal time: ${info['total_time']}\r\n";
echo "$text\r\n";
if ($httpCode == 200) {
//...
}
curl_multi_remove_handle($MultiCURL, $ch);
curl_close($ch);
}
}

?>

Всё, вроде бы, хорошо, работает, и даже довольно шустро, только вот процессор во время этой своей работы грузит неслабо. А это противоречит условиям задачи, да и вообще не соответствует логике алгоритма и обещаниям документации.

Начал разбираться и обнаружил ужасы.

Во-первых, вызов curl_multi_select(), вместо того, чтобы долго висеть в ожидании поступления ответов на запросы, завершается моментально и возвращает -1, что означает ошибку в общении со стеком операционки. И хотя почти вся остальная часть цикла при этом пропускается, процессор всё равно грузится.
И только после изрядного количества оборотов цикла -1 меняется на положительное число (ответ от сервера поступил).

Во-вторых, даже когда curl_multi_select() говорит, что данные пришли, curl_multi_info_read() возвращает false ("Нет никаких ответов").
И ещё сколько-то оборотов цикла должны прокрутиться, прежде чем curl_multi_info_read() соизволит-таки заметить пришедшие ответы. А значит, ещё бессмысленная нагрузка на процессор.

В-третьих, даже если curl_multi_info_read() увидела несколько (3-4-5) пришедших ответов, вовсе не факт, что их все удастся вычитать во внутреннем цикле. Нередко вычитываются только часть из пришедших, а остальные - на следующих проходах внешнего цикла.

Соответственно, вопросы:

1. Как сделать, чтобы curl_multi_select() висела до прихода ответов (как того обещает документация)?

2. Как сделать, чтобы curl_multi_info_read() вычитывала всё, что пришло, не откладывая часть "на потом"?

Частично я проблему бессмысленных циклов сгладил, вставив в цикле задержку на полсекунды (usleep(500000)) перед вызовом curl_multi_select(). Но это же костыль, а хочется, чтобы было по-человечески.
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2025 Invision Power Services, Inc.