Это вторая статья из серии статей о эмуляции многопоточности в языке PHP. Тут мы обсудим и посмотрим в действии библиотеку CURL.
При помощи CURL библиотеки можно присоединяться к разным серверам, используюя различные протоколы. С библиотекой CURL удобно работать и она имеет гибкие настройки.
Библиотека CURL реализует механизм мультизапросов (или множественных запросов). Принцип CURL заключается в отправке нескольких запросов, причём до отправки следующего запроса, не ожидается ответ на предыдущий.
Применим CURL в качестве способа для скачивания нескольких страниц.
Посмотрим для начала пример скачивания содержимого одного url-адреса:
$link = 'mail.ru';
$init = curl_init ('http://'.$link); // инициализируем Curl сеанс
curl_setopt ($init, CURLOPT_RETURNTRANSFER, 1); // curl_exec вернёт результат
curl_setopt ($init, CURLOPT_HEADER, 0); // HTTP-заголовок ответа не будет возвращён
$info = curl_exec ($init); // скачивание информации со страницы и её выдача браузеру
curl_close ($init); // закрываем Curl сеанс
В данном примере функция curl_init предназначена для инициализации CURL сеанса. В качестве параметра пишем URL страницы, которую будем скачивать. Затем, с помошью первого вызова функции curl_setopt ($init, CURLOPT_RETURNTRANSFER, 1) указываем, что результат нужно вернуть не выводя в браузер, а во втором вызове curl_setopt ($init, CURLOPT_HEADER, 0) запрещаем HTTP-ответ c сервера. Функция curl_setopt в параметрах принимает дескриптор подключения $init, опцию и значение этой опции. Благодаря функции curl_setopt можно указывать множество различных настроек для тонкого управления подключением. Подробнее обо всех опциях смотрите в документации к библиотеке Curl. Далее функцией curl_exec скачиваем содержимое заданного URL и, затем, закрываем подключение функцией curl_close. Переменной $info теперь содержит код скаченной страницы.
Теперь приведу пример скачивания сразу нескольких страниц (часть примера взята из официальной документации):
// URL-адреса страниц, которые надо скачать
$pages = array('google.ru', 'yahoo.com', 'yandex.ru', 'rambler.ru');
// инициализируем "контейнер" мультизапросов (мультикурл)
$multi_init = curl_multi_init();
// массив отдельных заданий
$job = array();
// проходим по каждому URL-адресу
foreach ($pages as $page) {
// подключаем отдельный поток (URL-адрес)
$init = curl_init('http://'.$page);
// если произойдёт перенаправление, то перейти по нему
curl_setopt($init, CURLOPT_FOLLOWLOCATION, 1);
// curl_exec вернёт результат
curl_setopt($init, CURLOPT_RETURNTRANSFER, 1);
// таймаут соединения 10 секунд
curl_setopt($init, CURLOPT_CONNECTTIMEOUT, 10);
// таймаут ожидания также 10 секунд
curl_setopt($init, CURLOPT_TIMEOUT, 10);
// HTTP-заголовок ответа не будет возвращён
curl_setopt($init, CURLOPT_HEADER, 0);
// добавляем дескриптор потока в массив заданий
$job[$page] = $init;
// добавляем дескриптор потока в мультикурл
curl_multi_add_handle($multi_init, $init);
}
// кол-во активных потоков
$thread = null;
// запускаем исполнение потоков
do {
$thread_exec = curl_multi_exec($multi_init, $thread);
}
while ($thread_exec == CURLM_CALL_MULTI_PERFORM);
// исполняем, пока есть активные потоки
while ($thread && ($thread_exec == CURLM_OK)) {
// если поток готов к взаимодествию
if (curl_multi_select($multi_init) != -1) {
// ждем, пока что-нибудь изменится
do {
$thread_exec = curl_multi_exec($multi_init, $thread);
// читаем информацию о потоке
$info = curl_multi_info_read($multi_init);
// если поток завершился
if ($info['msg'] == CURLMSG_DONE) {
$init = $info['handle'];
// ищем URL страницы по дескриптору потока в массиве заданий
$page = array_search($init, $job);
// скачиваем содержимое страницы
$job[$page] = curl_multi_getcontent($init);
// удаляем поток из мультикурла
curl_multi_remove_handle($multi_init, $init);
// закрываем отдельный поток
curl_close($init);
}
}
while ($thread_exec == CURLM_CALL_MULTI_PERFORM);
}
}
// закрываем мультикурл
curl_multi_close($multi_init);
Код содержит подробные комментирии, но нужно разобрать все по порядку.
Функцией curl_multi_init() мы инициализируем "контейнер" мультизапросов (мультикурл) для отдельных CURL соединений, с помощью которого можно параллельно проводить операции с ними.
Затем, циклом создаём соединение (или поток) для каждого URL-адреса из массива и добавляем его в мультикурл и массив заданий $job. В массив $job, ключи представляют собой URL-адреса нужных нам страниц, а значения – дескрипторы curl. Используя функцию curl_multi_add_handle, записываем отдельно созданное соединение к дескриптору нашего мультикурла.
Далее начинается цикл для запуска работы мультикурла. Одновременно запускаем на выполнение все потоки функцией curl_multi_exec и, при этом, в переменную $thread записываем количество потоков.
Ниже, в главном цикле, происходят основные действия. Цикл работает, пока есть незавершенные потоки или пока не произошла ошибка. Функцией curl_multi_select проверяем готовность какого-либо из потоков к дальнейшим с ним манипуляциям. Затем, функцией curl_multi_info_read считываем информацию о потоке. Функция curl_multi_info_read обновляет полученную информацию после вызова curl_multi_exec.
Возвращаем массив, применяя функцию curl_multi_info_read, в котором нам нужны ключи 'handle' и 'msg'. Благодаря 'msg' мы узнаём, закончил ли работу поток, а по 'handle' получаем его дескриптор. Узнав дескриптор потока, ищем в массиве заданий, к какому он принадлежит URL, затем сохраняем вместо него полученное содержимое страницы, используя функцию curl_multi_getcontent.
Теперь можно удалить поток и закрыть сам мультикурл. Закрываем мультикурл после выполнения всех потоков функцией curl_multi_close. Массив заданий $job теперь содержит HTML-коды нужных страниц.
Надеюсь, теперь стала более понятна эмуляция многопоточности в языке PHP. А в следующей статье мы познакомимся с примером использования stream-функций для решения нашей задачи.