Мультипоточное скачивание из сети на PHP с использованием curl


Содержание

curl_multi_exec — Запускает под-соединения текущего дескриптора cURL

curl_multi_exec — Запускает под-соединения текущего дескриптора cURL

Описание

Обрабатывает каждый дескриптор в стеке. Этот метод может быть вызван вне зависимости от необходимости дескриптора читать или записывать данные.

Список параметров

Мульти-дескриптор cURL, полученный из curl_multi_init() .

Ссылка на флаг, указывающий, идут ли еще какие-либо действия.

Возвращаемые значения

Здесь возвращаются ошибки, относящиеся только ко всему стеку. Проблемы все еще могут произойти на индивидуальных запросах, даже когда эта функция возвращает CURLM_OK .

Примеры

Пример #1 Пример использования curl_multi_exec()

Этот пример создаст два дескриптора cURL, добавит их в набор дескрипторов, а затем запустит их асинхронно.

// создаем оба ресурса cURL
$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 , «http://www.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 );

$active = null ;
//запускаем дескрипторы
do <
$mrc = curl_multi_exec ( $mh , $active );
> while ( $mrc == CURLM_CALL_MULTI_PERFORM );

//закрываем дескрипторы
curl_multi_remove_handle ( $mh , $ch1 );
curl_multi_remove_handle ( $mh , $ch2 );
curl_multi_close ( $mh );

Смотрите также

  • curl_multi_init() — Создает набор cURL-дескрипторов
  • curl_multi_select() — Ждет активности на любом curl_multi соединении
  • curl_exec() — Выполняет запрос cURL

Асинхронная загрузка с использованием Curl в curl_multi_exec ()

Я пытаюсь в течение нескольких дней выяснить, как было бы возможно, если бы вообще можно было загружать несколько файлов параллельно, используя PHP.

учитывая, что у меня есть класс с именем Request с 2 методами register () и executeAll ():

Проблема:

Не могу понять, почему в upload.php (скрипт, который является моим URL-адресом конечной точки в методе register) $ _FILES всегда является пустым массивом:

Вещи, которые я уже пробовал:

префикс данных с @, вот так:

Это, к сожалению, не сработало.

Что я делаю неправильно ?

Как я могу получить файловый ресурс, хранящийся в $ _FILES global в опубликованном скрипте (upload.php)?

Дополнительная информация отладки:

Применение cURL и libcurl в php

Вступление

Это руководство предназначено для тех web-разработчиков, которые хотят автоматизировать передачу файлов по сети либо взаимодействовать с другими Интернет — сервисами. Для чтения этой главы вам необходимо владеть пониманием принципа работы стратегии клиент — сервер и знать основы синтаксиса PHP.

cURL и libcurl — библиотеки, позволяющие серверу передавать файлы на удаленный компьютер, используя множество Интернет протоколов. Библиотеки имеют очень гибкую настройку и позволяют выполнить практически любой удаленный запрос. Используя их, web-сервер может выступать полноценным клиентом любого основанного на HTTP протоколе сервисе, к примеру: XML-RPC, SOAP, или WebDAV.

В этой статье будет рассказано о том, что такое cURL, как его использовать из командной строки, и о его применении в PHP.

Что такое cURL и libcurl

cURL является сокращением от «Client URLs». Он был разработан Daniel Stenberg в 1998 году как утилита, работающая из командной строки. Libcurl — переносимая библиотека, предоставляющая простой API-интерфейс к функциональности cURL. Библиотека безопасна в мультипоточной среде, совместима с IPv6 и поддерживает постоянные соединения. Интерфейс взаимодействия с php был добавлен Sterling Hughes.

cURL и libcurl могут использоваться для передачи информации с использованием таких протоколов, как HTTPS, FTP, FTPS, GOPHER, LDAP, DICT, TELNET и FILE. Реализована поддержка практически всех *nix систем, а также Windows, OS/2, BeOS и некоторых других.

Библиотека curl — Open Source продукт с оригинальной MIT/X лицензией, позволяющей использовать этот пакет в любых: как коммерческих, так и некоммерческих целях, включать ее в свой дистрибутив (даже если он распространяется без открытого исходного кода).

Необходимо понимать, что cURL не имеет ничего общего с Curl Corporation, являющейся коммерческим производителем языка программирования Curl.

Инсталляция cURL

Для работы в PHP версии 4.2.3 и выше вам необходим cURL версии не ниже 7.9.0. Для работы в PHP версии 4.3.0 и выше вам необходим cURL версии не ниже 7.9.8.

Windows

Как и любой другой дополнительный модуль, он требует проинсталлированного дистрибутива PHP. Для установки cURL скопируйте файлы php4ts.dll, ssleay32.dll, php_curl.dll, msvcrt.dll из каталога DLL в системную директорию Windows, как правило, это:

После этого необходимо раскомментировать строку

в файле php.ini, либо подгружать модуль динамически, во время работы скрипта.

Ближайшее зеркало, содержащее исходные коды и откомпилированные бинарные файлы для различных операционных систем, вы можете найти на сайте http://curl.haxx.se/ .

Поскольку cURL использует библиотеку openssl для SSL соединений, вначале необходимо установить на север SSL. В случае, если при инсталляции cURL библиотека openssl найдена не будет, произойдет установка cURL без поддержки SSL соединений.

Инсталляция cURL состоит из следующих шагов: ./configure, make, make install.

После этого необходимо пересобрать PHP c опцией —with-curl

О том, включена ли поддержка cURL в php, вы можете узнать, выполнив phpinfo().

Пример использования cURL

Использование cURL из командной строки очень просто. Следующий пример запрашивает web-страницу и выводит ее в stdout

M(опция -L разрешает переадресации)

Также возможно использование cURL из командной строки при помощи PHP. Следующий пример запрашивает 3 страницы и выводит их на экран

Пример использования cURL в PHP

Использование libcurl в php-скрипте является достаточно простым, особенно для таких операций, как генерация POST-запросов.

Для использования libcurl необходимо выполнить следующие шаги:

  • Инициализировать сессию cURL
  • Установить опции cURL (порядок установки опций не имеет значения)
  • Выполнить запрос
  • Завершить сессию cURL

Для демонстрации вышеперечисленного приведем практические примеры использования cURL для генерации POST-запроса, HTTP-авторизации, FTP-сессии.

При возникновении проблем в использовании cURL необходимо добавить следующие строки перед вызовом curl_close для получения отчета о последнем выполненном запросе:

Решение о том, что использовать: cURL или libcurl, — стоит принимать в зависимости от обстоятельств. В случае, если Вы пишете выполняемый из командной строки скрипт, либо у Вашего провайдера нет поддержки libcurl, имеет смысл использовать cURL. В остальных случаях использование libcurl оказывается более удобным.

Скачивание файлов с сайтов при помощи PHP и CURL

CURL является отличным инструментом, для подключения к удаленным сайтам, что упрощает возможность отправки форм, загрузку целых страниц, или, как в нашем случае, загрузку файлов.

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

Примечание: чтобы упростить пример, в этой статье мы не будем выполнять проверку CURL запросов на ошибки. Вы же всегда должны делать это, функция curl_getinfo() главное подспорье в этом деле.

Вы можете прочитать ответ и записать его на диск, как показано в следующем листинге. $path место на сервере, где вы хотите, чтобы записать файл.

$ch = curl_init( $url );

curl_setopt( $ch , CURLOPT_RETURNTRANSFER, true);

$data = curl_exec( $ch );

file_put_contents ( $path , $data );

Существует, однако, проблема с этим кодом. В данном примере, файл пишеться на диск не напрямую, а сперва целиком загружается в оперативную память. Я думаю здесь понятно, что можно запросто умереться в предел памяти для исполнения скрипта и схлопотать fatal error. Так что данный способ годиться если вы скачиваете маленькие файлы, мегабайт до 20-30.

Примечание: Даже если ваш лимит памяти установлен очень высоко, грузить сервер лишний раз тоже не хорошо.

В таком случае опять возложим всю черную работу на CURL и заставим его писать данные прямо в файловый поток. Сделать это можно с помощью директивы CURLOPT_FILE.

Илон Маск рекомендует:  Недокументированные возможности ms dos

Для этого вы должны сначала создать новый указатель файла с помощью fореn(). Далее мы передаем этот указатель на файл для выполнения запроса. Наконец, мы закрываем файл.

$fp = fopen ( $path , ‘w’ );

$ch = curl_init( $url );

curl_setopt( $ch , CURLOPT_FILE, $fp );

$data = curl_exec( $ch );

Вот и все, что нужно сделать, теперь можно не бояться о превышение лимита памяти для исполнения скрипта.

Примеры использования cURL в PHP

GET запрос

POST запрос

file_get_contents() так же умеет отправлять POST запросы:

PUT запрос

DELETE запрос

HTTP-метод DELETE используется в REST API для удаления объектов.

Отправка файлов на другой сервер

Отправка файлов осуществляется методом POST :

С PHP 5.5 такой метод загрузки файлов вызывает ошибку, поэтому следует применять CURLFile.

Также через curl можно отправить сразу несколько файлов:

Еще файлы можно отправить методом PUT , например так загружаются файлы в REST API Яндекс Диска.

Скачивание файлов

Curl позволяет сохранить результат сразу в файл, указав указатель на открытый файл в параметре CURLOPT_FILE .

Чтобы CURL сохранял куки в файле достаточно прописать его путь в параметрах CURLOPT_COOKIEFILE и CURLOPT_COOKIEJAR .

Передать значение кук можно принудительно через параметр CURLOPT_COOKIE .

Имитация браузера

На многих сайтах есть защита от парсинга. Она основана на том что браузер передает серверу user agent , referer , cookie . Сервер проверяет эти данные и возвращает нормальную страницу. При подключение через curl эти данные не передаются и сервер отдает ошибку 404 или 500. Чтобы имитировать браузер нужно добавить заголовки:

HTTP авторизация

Если на сервере настроена HTTP авторизация, например с помощью .htpasswd, подключится к нему можно с помощью параметра CURLOPT_USERPWD .

Урок по cURL: основы использования и пара полезных трюков (часть первая)

Pirnazar

Well-known member

Для чего нужна cURL

  • cURL отлично подходит для имитации действий пользователя в браузере.


Реальный практический пример: вам нужно перезагрузить роутер (модем) для смены IP адреса. Для этого нужно: авторизоваться в роутере, перейти к странице обслуживания и нажать кнопку «Перезагрузка». Если это действие нужно выполнить несколько раз, то процедуру нужно повторить. Согласитесь, делать каждый раз в ручную эту рутину не хочется. cURL позволяет автоматизировать всё это. Буквально несколькими командами cURL можно добиться авторизации и выполнения задания на роутере.

  • cURL удобен для получения данных с веб-сайтов в командной строке.

Ещё один практический пример: мы хотим реализовать показ общей статистики для нескольких сайтов. Если использовать cURL, то это становится вполне тривиальной задачей: с помощью cURL мы проходим аутентификацию на сервисе сбора статистики (если это требуется), затем (опять же командами cURL) получаем необходимые страницы, парсим нужные нам данные; процедура повторяется для всех наших сайтов, затем мы складываем и выводим конечный результат.

Т.е. случаи использования cURL вполне реальные, хотя, в большинстве, cURL нужна программистам, которые используют её для своих программ.

cURL поддерживает множество протоколов и способов авторизации, умеет передавать файлы, правильно работает с кукиз, поддерживает SSL сертификаты, прокси и очень многое другое.

cURL в PHP и командной строке

Мы можем использовать cURL двумя основными способами: в скриптах PHP и в командной строке.

Чтобы включить cURL в PHP на сервере, необходимо в файле php.ini раскомментировать строку

А затем перезагрузить сервер.

На Linux необходимо установить пакет curl.

На Debian, Ubuntu или Linux Mint:

Чтобы наглядно было видно разницу в использовании в PHP и в командной строке, будем одни и те же задачи выполнять дважды: сначала в скрипте PHP, а затем в командной строке. Постараемся при этом не запутаться.

Получение данных при помощи cURL

Получение данных при помощи cURL в PHP

Всё очень просто:
$target_url — адрес сайта, который нас интересует. После адреса сайта можно поставить двоеточие и добавить адрес порта (если порт отличается от стандартного).

curl_init — инициализирует новый сеанс и возвращает дискриптор, который в нашем примере присваивается переменной $ch.

Затем мы выполняем запрос cURL функцией curl_exec, которой в качестве параметра передаётся дискриптор.

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

Чуть дополним наш скрипт:

У нас появилась строчка curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);.

curl_setopt — задаёт опции. Полный список опций можно найти на этой странице:

Теперь значение скрипта присваивается переменной $response_data, с которой можно проводить дальнейшие операции. Например, можно вывести её содержимое.

служат для отладки, на случай возникновения ошибок.

Получение данных при помощи cURL в командной строке

В командной строке достаточно набрать

где вместо mi-al.ru — адрес вашего сайта.

Если нужно скопировать данные в переменную, а не выводить полученный результат на экран, то делаем так:

Чтобы они не выводились, добавляем ключ -s:

Мультипоточное скачивание из сети на PHP с использованием curl

Мне нужно было разобраться, как именно работают функции curl_multi_*, представленные в PHP, почему так разнятся примеры не только в инете, но даже в справке PHP, что на самом деле происходит. CURL в PHP — это инкапсуляция настоящих функций библиотеки и еще некоторых других. При этом PHP использует не все функции libcurl, да и справка оставляет желать лучшего. Дабы не тыкать вслепую пимпы на клаве, перебирая варианты чужого кода, я решил постичь истинный смысл функций мульти-cURL и их параметров.

API библиотеки libcurl на С

По большей части здесь представлен перевод страниц сайта curl.haxx.se с моими соображениями в дополнение. Многие из приведенных здесь функций окажутся неизвестными для PHP-кодера. Cвязь двух языков рассмотрена ниже.

Основная часть

Пару слов о терминологии (без претензии на абсолютную правильность):

  • если вам неудобно слово «трансфер«, можете заменить на «передача«. Как угодно.
  • простой дескриптор («easy handle») — это то, что создается функцией curl_init(), т.е. дескриптор объекта cURL.
  • аббревиатура «cURL» и название «libcurl» в этой статье означают одно и тоже.
  • здесь речь пойдет в основном о мульти-интерфейсе библиотеки libcurl, если не указано иное. Т.е. статья про набор функций cURL для организации множества одновременных запросов к web-серверу. Что мне непонятно: говорим об интерфейсе, т.е. заготовках, «контракте» на наличие функций и их точное описание. А где собственно реализация этих функций, где конкретный код? Нужно учесть, что речь о использовании cURL на C, где, строго говоря, вообще нет интерфейсов. Может в таком случае можно приравнять понятие интерфейса к коду функций? Или я просто путаю понятия :)

Простой интерфейс libcurl представляет собой синхронный интерфейс, который передает один файл за раз и не возвращает результат, пока не закончит. Мульти-интерфейс позволяет вашей программе передавать много файлов в обоих направлениях одновременно, не принуждая вас использовать много потоков. Из названия может показаться, что мульти-интерфейс предназначен для многопоточных приложений (игра слов английского языка — «multi interface» и «multi-threaded«), но на деле все почти наоборот. Мульти-интерфейс позволяет однопоточному приложению выполнять такие же виды множественных, одновременных трансферов, какие может выполнять многопоточная программа. Он позволяет получить выгоды многопоточных трансферов без сложностей управления и синхронизации многих потоков.

Чтобы использовать этот интерфейс, вам лучше сначала понять, как пользоваться простым интерфейсом libcurl (здесь не рассказываю). Мульти-интерфейс это простой способ выполнить множество трасферов одновременно путем добавления простых дескрипторов в «мульти стек» (далее — «стек»).

Примерный порядок работы такой: создать простые дескрипторы, описать все нужные для них опции (для каждого дескриптора могут быть свои опции). Далее создать мульти-дескриптор функцией curl_multi_init(), затем добавить в него простые дескрипторы вызовом curl_multi_add_handle(). Замечание: во избежание глюков простой дескриптор нельзя использовать, пока он находится в стеке. Когда добавлены все дескрипторы на текущий момент (можно добавить еще в любое время), запустить трансферы вызовом curl_multi_perform().

Илон Маск рекомендует:  Что такое код imap_mailboxmsginfo

CURLMcode curl_multi_perform (CURLM *multi_handle, int *running_handles)

«perform» (англ). — выполнять. Функции curl_multi_init и curl_multi_add_handle нормально описаны в справке PHP. «Аналогом» curl_multi_perform() является curl_multi_exec.

Тип CURLMcode — это тот же integer, только значения соответствуют кодам ошибок мульти-cURL. Коды ошибок cURL вместе с пояснениями (на английском).

Функция curl_multi_perform() является ассинхронной. Она выполняется настолько мало, насколько это возможно, и возвращает контроль ваше программе. Она разработана так, чтобы никогда не блокировать (полагаю, речь о блокировании программы — прим. пер. ).

Одна из основных идей мульти-интерфейса — позволить вашему приложению управлять ситуацией. Вы управляете трансферами вызывая curl_multi_perform(). После этого libcurl начнет передачу данных, если будет что передавать. Она будет использовать callback-функции и все, что вы указали в настойках простых дескрипторов (см. функцию curl_easy_setopt() или PHP-аналог curl_setopt()). Она будет передавать данные по всем тем трансферам в стеке, которые будут готовы передавать что-либо. Это может быть весь стек, а может ни одного трансфера.

До версии 7.20.0: если в функция возвращает CURLM_CALL_MULTI_PERFORM, обычно это означает, что вам нужно вызвать curl_multi_perform() снова, до select() или других действий (об этом чуть позже). Не обязательно делать это немедленно, но этот код возврата означает, что у libcurl возможно есть еще данные для возврата или отправки перед тем, как она будет «удовлетворена».

На заметку, функция curl_multi_perform() возращает CURLM_CALL_MULTI_PERFORM только когда хочет немедленно быть вызванной снова. Если все в порядке и нет ничего срочного, то функция возвращает CURLM_OK и вам нужно ждать активности, чтобы вызвать эту функцию снова.

Как я не извращался, не смог получить другие CURLM_* коды, кроме CURLM_CALL_MULTI_PERFORM и CURLM_OK. Устойчивая система :)

В своем втором параметре эта функция хранит количество еще работающих трансферов (а не флаг, как сказано в PHP-справке), т.е. тех, кто еще не закончил прием/передачу данных. Трансферы могут завершаться ошибкой передачи, это тоже вариант. Когда какой-то трансфер завершится, счетчик уменьшится. Когда он станет равен 0, значит все транферы завершились.

Любопытный момент, который я обнаружил экспериментально: похоже, curl_multi_add_handle() кидает в стек копию простого дескриптора. К примеру, если закрыть дескриптор сразу после добавления, multi_curl_perform() все равно его запустит и обработает. Даже передача по ссылке, типа curl_multi_add_handle($mh, &$ch2), не меняет этот факт.

Функцию curl_multi_info_read() можно использовать для получения информации по завершенным трансферам.

CURLMsg *curl_multi_info_read ( CURLM *multi_handle, int *msgs_in_queue)

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

Если нужно остановить передачу по одному простому дескриптору в стеке, используйте curl_multi_remove_handle. После curl_multi_remove_handle() все еще можно использовать простой дескриптор для одиночных функций cURL, типа curl_exec() и т.п. Поэтому исключения дескриптора из стека не достаточно для корректного завершения работы. Нужно вызывать еще curl_easy_cleanup() для каждого дескриптора. После всей работы нужно закрыть мульти-дескриптор вызовом curl_multi_cleanup(). PHP-аналоги: curl_close и curl_multi_close.

Высший пилотаж

Сказанного выше уже достаточно, чтобы осознано использовать функции curl_multi_* в PHP. Однако, чтобы скрипт по минимуму использовал ресурсы сервера, а код выглядел более профессионально, нужно знать еще пару вещей.

Небольшой экскурс в язык программирования C. int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);

Функция select() (или pselect()) — это ключевая функция для программ на C, обрабатывающих несколько файловых дескрипторов (или сокетов) одновременно. Массивы файловых дескрипторов называются наборами дескрипторов. Каждый набор объявлен как fd_set (структурный тип). Обычно select() используется для ожидания изменения статуса у одного или нескольких файловых дескрипторов. Изменение статуса происходит, когда в дескрипторе появляются данные для чтения, или когда во внутренних буферах ядра появляется место и в файловый дескриптор может быть произведена запись, или когда в дескрипторе происходит ошибка (в случае сокета или канала (pipe) такая ошибка возникает при закрытии другого участника соединения). Проще говоря, функция select() мониторит множество дескрипторов одновременно и своевременно погружает процесс в состояние сна при отсутствии активности. ( по материалам сайта amax.h16.ru) )

Если ни один из заданных дескрипторов не готов к назначенным действиям, то select() блокирует скрипт, пока хотя бы один дескриптор не будет готов, или пока не наступит таймаут, или выполнение не прервется сигналом. Параметр timeout определяет, как долго функция select() должна ждать. Если время вышло, функция должна завершиться.

int poll (struct pollfd fds[], nfds_t nfds, int timeout);

Функция poll() определит файловые дескрипторы готовые для чтения/записи данных, или имеющие другие события. В последнем параметре функции устанавливается таймаут ожидания в миллисекундах. Если проверка сразу не выявила активности на выбранных файловых дескрипторах, poll() должна ждать активности на дескриторах, как минимум, указанный период времени. Если таймаут установлен 0, то poll() должна сразу закончить свою работу и вернуть значение; если -1, тогда приложение блокируется, пока не наступит требуемое событие или вызов функции не будет прерван.

Теперь самое интересное. Ваше приложение может узнать у libcurl, когда библиотека готова к вызову для передачи данных. Поэтому не нужно дергать в цикле curl_multi_perform() до безумия. Функция curl_multi_fdset() предлагает интерфейс, которым вы можете извлекать наборы дескрипторов типа fd_set из libcurl, чтобы использовать вызовы select() или poll() для получения информации, когда трансферам в стеке возможно нужно внимание.

CURLMcode curl_multi_fdset (CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd)

Очень важный момент здесь. Функция curl_multi_fdset() извлекает информацию о файловых дескрипторах из переданного ей мульти-дескриптора. Ее вызов заполнит набор fd_set конкретными файловыми дескриторами, используемыми интрефейсом libcurl в данный момент. Если в libcurl не задано ни одного файлового дескриптора, параметр функции max_fd будет содержать -1. Это значит, что libcurl делает что-то, что нельзя мониторить приложением через сокет. В этом случае вы не можете узнать, когда точно закончится текущее действие с использованием select(). Поэтому когда max_fd=-1, вам нужно немного подождать и затем вызвать curl_multi_perform() в любом случае. Сколько ждать? Я бы предложил 100 мс, как минимум, но вы можете сами найти наиболее подходящее значение.

Как выглядит на практике описанное выше? Если libcurl в момент запроса по select() с установленным таймаутом вернет max_fd=-1, то программа зависнет на весь таймаут, не зависимо от дальнейшей активности файловых дескрипторов. Потом все заработает корректно. Т.е. указал таймаут 10 секунд, висим 10 секунд, потом работаем без задержек. Этот ньюанс может проявиться, если web-сервер стоит на медленной машине или при сильной загрузке нормального сервера. К примеру, у меня на локальной машине был косяк с зависанием, а на сервере хостера — нет.

Большинство приложений использует curl_multi_fdset() для получения файловых дескрипторов из мульти-дескриптора libcurl. Затем приложение ждет каких-либо действий используя select(), и когда хотя бы один дескриптор готов, вызывает curl_multi_perform().

Итак, вызываем curl_multi_fdset(), затем select(). Она вернет значение, когда один из файловых дескрипторов даст сигнал действия, и тогда вызываем curl_multi_perform(), чтобы позволить libcurl сделать то, что она хочет сделать. В этом суть контроля над ситуацией. А теперь ложка дегтя в эту кашу: примите во внимание, что у libcurl имеется своя возможность следить за таймаутом реакции, поэтому не следует назначать слишком длинное ожидание в select(), и нужно вызывать время от времени curl_multi_perform(), даже если не было сигналов от файловых дескрипторов (дословный перевод). Вот это миленько: какое точное понятие в программировании — «не слишком долго, время от времени». Замечу, что по умолчанию таймаут select() — 1сек, таймаут libcurl на ожидание реакции на сокетах

5 мин (на форумах вычитал). Еще одно предостережение: обязательно вызвайте curl_multi_fdset() сразу перед вызовом select(), т.к. текущий набор файловых дескрипторов мог измениться с прошлого вызова функций libcurl.

Лирическое отступление. Вопрос с правильным таймаутом оказался практически не разрешимым :( На том же сайте нашел следующее: «когда выполняете select(), вам следует использовать curl_multi_timeout() для выяснения нужного времени ожидания действия. Вызовите curl_multi_perform() даже если нет активности на указанных файловых дескрипторах по истечении таймаута«. Так вот, функция curl_multi_timeout() выясняет значение либо через вызов curl_multi_socket_action() задав второму параметру значение CURL_SOCKET_TIMEOUT, либо через вызов той же curl_multi_perform()! В пером случае без бутылки не разобраться, но все-таки можно докопаться до истины. Но каким образом вызов curl_multi_perform установит таймаут для select, я не понял :( Более того, покопавшись в исходниках PHP, я выяснил, что там таймаут расчитывается математикой от переданного параметра $timeout (double), затем задается в select(). Т.е. ни каких дополнительных вызовов curl_multi_* в PHP не делается.

Илон Маск рекомендует:  md5_file - Возвращает MD5-хэш файла

Вся прелесть в том, что описанные заморочки с curl_multi_fdset(), select() и иже с ними инкапсулированы в одну функцию PHP — curl_multi_select().

Реализация на PHP

Рассматриваем PHP 5.3.8, cURL 7.20.0. Ниже приведен список некоторых функций cURL в PHP и их связь с «настоящими» функциями библиотеки. В исходниках PHP помимо вызова «настоящих» функций libcurl есть еще обращения к zend_* и много того, что мне непостижимо. Но главную суть я уловил и подтвердил свои догадки. Что обо всем этом написано в справке PHP, можно узнать здесь.

cURL на PHP Инкапсуляция (код С)
int curl_multi_exec (resource $mh,
int &$still_running)
curl_multi_perform (mh->multi, &still_running)
RETURN_LONG
В текущей версии PHP не используется curl_multi_socket()
int curl_multi_select ( resource $mh
[, float $timeout = 1.0 ] )
_make_timeval_struct (&to, timeout); расчет таймаута (внутренняя функция)
curl_multi_fdset (mh->multi, &readfds, &writefds, &exceptfds, &maxfd);
select (maxfd+1, &readfds, &writefds, &exceptfds, &to);
RETURN_LONG
void curl_multi_close (resource $mh) curl_multi_cleanup (mh->multi);
bool curl_setopt (resource $ch,
int $option, mixed $value)
curl_easy_setopt (ch->cp, option, value);
return 1;
void curl_close (resource $ch) curl_easy_cleanup (ch->cp);
Практика

Теперь зная, откуда «ноги растут», можно создать правильный код на PHP. Приведенный мной пример не претендует на единственно возможный, это один из многих рабочих вариантов использования мульти-cURL. Весь код здесь не привожу, см. в этом архиве. Код в архиве немного другой, больше служебной информации выдает в браузер.

Пример создан для демонстрации теоретического материала, изложенного выше. Он состоит из двух скриптов: test.php возвращает текущее время. Если ему передан в GET-запросе параметр p=NN, то перед ответом скрипт ждет NN секунд. Это эмуляция разного времени ответа от web-серверов в реальных запросах. Все для того, чтобы показать одновременную работу сURL со всеми заданными адресами. Второй скрипт архива, query.php — собственно сама программа с мульти-cURL.

Если в строке 24 будет значение $sel = 1, значит найден один файловый дескриптор, готовый к передаче. Вполне может быть, что их будет несколько и значение будет больше. Если $sel = 0, значит таймаут истек, но нет ни одного готового дескриптора. Значение $running — количество еще активных простых дескрипторов cURL.

В строке 25 стоит пауза. Это не «по учебнику», я придумал такую хитрость, чтобы дожидаться окончания одновременных запросов. Они обычно отстают друг от друга где-то на 0.5 секунды и такая пауза уменьшает количество итераций основного цикла. На практике она может оказаться бесполезной, можно смело ее убрать.

Функцию curl_multi_info_read() засунул в цикл (строка 31), т.к. есть вероятность, что несколько дескрипторов закончатся одновременно (особенно с паузой выше), тогда их нужно обработать в одном шаге основного цикла.

В примере я ставил таймаут select-а (строка 24) на несколько секунд только потому, что ответ предполагается ждать долго. На практике можно явно не указывать таймаут, тогда он будет равен 1сек. Все зависит от области применения. Одно могу сказать точно, curl_multi_select() нужен в коде. Он заметно разгружает скрипт и ресурсы сервера.

Проблема этого примера в том, что приходится «ловить лису за хвост». Сервер хостера работает в разы шустрее, чем моя машина. Поэтому чтобы понять смысл кода, придется экспериментировать с таймаутом select-а, GET-запросом и паузой после curl_multi_select(). Если вам все это до лампочки, то на практике можно просто использовать код, как есть.

[UPD] Прошло время, и сам не понял ход своих мыслей =) Строки 19-20. Сначала перечитываем «Очень важный момент», там два абзаца. Теперь поясняю: пауза нужна, чтобы пропустить переходное состояние libcurl между статусами *_PERFORM и *_OK, когда библиотека не вернет ни одного дескриптора и значит нечего разбирать в последующем цикле. Вот в чем была идея :) Кроме того, без этой паузы получим зависание на первом шаге цикла в строке 24, пока таймаут, заданный в *_select() не кончится. Все это наблюдалось на медленной машине. Теперь ОЗУ прибавилось, не получается подтвердить слова.

Однако это не все странности :( Специально погонял этот код с отладочными var_dump(). Смотрите строки 17-20 (первый цикл и повторный вызов curl_multi_exec()). Знаете, что будет возвращать curl_multi_exec() на последних шагах цикла? CURLM_CALL_MULTI_PERFORM, затем FALSE (а не CURLM_OK, как ожидалось), что приводит к выходу из цикла. Далее вызов в 20-й строке вернет CURLM_OK, на быстрой машине даже пауза не нужна.

А вот еще одна загадка: в строке 28 прописан тот же самый цикл опроса libcurl, что был в начале (с17). Но после него $status никогда не содержит FALSE, всегда только CURLM_OK! Ну и что, спрашивается, делает библиотека после запуска дескрипторов, раз не может сразу вернуть правильный статус при запуске процесса?

Есть более короткий пример использования мульти-cURL, основанный на первой части статьи. Забьем на всякие ожидания и т.п. и получим следующее (источник + мои доработки):

[1oo%, EoF]
Похожие материалы: cURL — ключ от всех дверей
Понравилась статья? Расскажите о ней друзьям:

Использование curl_multi_exec в PHP

Привет всем! В статье рассматривается использование curl_multi_exec в PHP для реализации множественных запросов.

Скорее всего, вы использовали cURL в PHP для выполнения запросов один за одним. При этом общее время выполнения складывалось из времени выполнения всех запросов.

Допустим, ваше PHP приложение должно делать запросы к нескольким серверам, либо к одному серверу передавая разные данные. Таким образом, нам нужно делать cURL запрос к каждому из серверов, и это увеличивает общее время запроса.

Используя curl_multi_exec, мы можем выполнять все эти запросы параллельно, тем самым общее время запроса будет равно времени самого медленного из запросов.

Пример

Приведем пример, в котором нам нужно запрашивать записи с сервера по ID.

Используя curl мы должны для каждого ID в цикле делать запросы к серверу. Если у нас есть 10 ID, то получаем 10 последовательных запросов и 10ти-кратное увеличение общего времени запроса:

Но если мы будем использовать параллельные запросы cURL, то общее время запроса значительно уменьшается:

Результатом каждого из cURL запроса будет находиться в переменной $result, например вот так:

Curl скачивание файла с стороннего сайта

Но в итоге получаю пустой файл

Добавлено через 4 часа 16 минут
Бамп. Мазафаки, хелп ми. Очиииин нужна.

30.09.2020, 20:04

Данные с стороннего сайта (Curl)
Здравствуйте! Получаю данные с сайта с помощью curl. Возможно ли, как-нибудь в случае изменения.

Скачивание бинарного файла с помощью curl
Добрый день. Хочу с помощью курл скачивать бинарные файлы (*.apk — приложения для андроид) с.

Скачивание файла на хост с сайта
Всем доброго времени суток! Задача на засыпку. Есть Я, есть мой сайт и есть сервис youtube.

Получить код сайта curl
Добрый день! Пытаюсь получить код страницы через curl: $Hea = array( «Host.

Можно ли использовать cURL для потоковой загрузки файла с помощью POST?

Или PHP не позволяет это? Я прочитал, что можно использовать PUT, но сервер ожидает только POST.

Да, можно потоковое загрузить файл, используя загрузку файлов POST с помощью cURL. Это значение по умолчанию, и вам необходимо указать имя файла (ов), который вы хотите передать в виде строк.

Помимо этого метода по умолчанию невозможно использовать cURL для потоковой загрузки файла с использованием метода POST.

Curl уже поддерживает поток, попробуйте curl —help | grep binary , и вы получите:

«- двоичные данные двоичных данных DATA HTTP POST (H)»

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL