«Многопоточность» в PHP (curl)

Использование библиотеки curl.

Эта статья является первой из серии “Многопоточность” в PHP

Curl – это библиотека, позволяющая подсоединяться к разным серверам по разным протоколам. Обладает удобством в работе и способностью гибко настраиваться.
Curl реализует механизм множественных запросов, или мультизапросов. Его принцип заключается в том, что посылается несколько запросов, при этом перед отправкой следующего не ожидается ответ на предыдущий.

Используем это в нашем примере скачивания нескольких страниц.

Рассмотрим сначала процесс скачивания содержимого с одного url.

<?php
$url = 'mail.ru';
// инициализация сеанса curl
$ch = curl_init('http://'.$url);
// curl_exec будет возвращать результат
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// не будет возвращаться http-заголовок
curl_setopt($ch, CURLOPT_HEADER, 0);
// загрузка страницы и выдача её браузеру
$content = curl_exec($ch);
// завершение сеанса и освобождение ресурсов
curl_close($ch);
?>

Здесь функцией curl_init мы инициализируем сеанс curl и в качестве параметра передаем урл страницы, которую хотим скачать. Далее первым вызовом функции curl_setopt говорим, что результат надо вернуть, а не вывести в браузер, и вторым запрещаем передачу нам http-ответа сервера. curl_setopt принимаюет в качестве параметров дескриптор соединения $ch, название опции и ее значение соответственно. С помощью curl_setopt можно задать много параметров для более тонкого управления соединением, подробнее читайте в мануале к этой библиотеке. Затем функцией curl_exec производим собственно скачивание и в завершении закрываем соединение – curl_close. В переменной $content у нас теперь находится код страницы, указанной в $url.

А сейчас давайте попробуем скачать сразу несколько страниц (основа примера взята из документации на php.net):

<?php

// страницы, содержимое которых надо получить
$urls = array('yandex.ru', 'google.ru', 'mail.ru', 'rambler.ru');

// инициализируем "контейнер" для отдельных соединений (мультикурл)
$cmh = curl_multi_init();

// массив заданий для мультикурла
$tasks = array();
// перебираем наши урлы
foreach ($urls as $url) {
        // инициализируем отдельное соединение (поток)
        $ch = curl_init('http://'.$url);
        // если будет редирект - перейти по нему
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        // возвращать результат
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        // не возвращать http-заголовок
        curl_setopt($ch, CURLOPT_HEADER, 0);
        // таймаут соединения
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        // таймаут ожидания
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        // добавляем дескриптор потока в массив заданий
        $tasks[$url] = $ch;
        // добавляем дескриптор потока в мультикурл
        curl_multi_add_handle($cmh, $ch);
}

// количество активных потоков
$active = null;
// запускаем выполнение потоков
do {
        $mrc = curl_multi_exec($cmh, $active);
}
while ($mrc == CURLM_CALL_MULTI_PERFORM);

// выполняем, пока есть активные потоки
while ($active && ($mrc == CURLM_OK)) {
        // если какой-либо поток готов к действиям
        if (curl_multi_select($cmh) != -1) {
                // ждем, пока что-нибудь изменится
                do {
                        $mrc = curl_multi_exec($cmh, $active);
                        // получаем информацию о потоке
                        $info = curl_multi_info_read($cmh);
                        // если поток завершился
                        if ($info['msg'] == CURLMSG_DONE) {
                                $ch = $info['handle'];
                                // ищем урл страницы по дескриптору потока в массиве заданий
                                $url = array_search($ch, $tasks);
                                // забираем содержимое
                                $tasks[$url] = curl_multi_getcontent($ch);
                                // удаляем поток из мультикурла
                                curl_multi_remove_handle($cmh, $ch);
                                // закрываем отдельное соединение (поток)
                                curl_close($ch);
                        }
                }
                while ($mrc == CURLM_CALL_MULTI_PERFORM);
        }
}

// закрываем мультикурл
curl_multi_close($cmh);

?>

Код подробно откомментирован, но давайте разберем все по порядку.
В 7 строчке мы инициализируем контейнер для отдельных соединений curl (далее я буду называть его мультикурл), именно он позволит нам проводить операции с ними параллельно.
Далее в цикле инициализируем соединение (назовем его поток) для каждого урла из нашего массива, попутно добавляя его в мультикурл и в массив заданий $tasks. $tasks – массив, в котором ключами являются адреса наших страниц, а значениями – соответствующие дескрипторы curl. Функция curl_multi_add_handle добавляет к дескриптору нашего мультизапросного соединения отдельное созданное соединение.

В 34 строке запускается цикл для начала работы нашего мультикурла. Функция curl_multi_exec одновременно отправляет на выполнение все объявленные потоки, при этом в переменную $active заносится количество выполняемых потоков.

В основном цикле, начинающемся на 40 строчке, происходят главные действия. Он выполняется до тех пор, пока есть незавершенные потоки или пока не произошла ошибка. В 42 строке вызывается функция curl_multi_select, которая проверяет готовность какого-либо из потоков к дальнейшим действиям с ним. Затем, в 47 строке, функцией curl_multi_info_read получаем информацию о потоке. Но так как curl_multi_info_read обновляет возвращаемую информацию только после вызова curl_multi_exec, сделаем это в строке 45.

Функция curl_multi_info_read возвращает массив, в котором нас интересуют ключи ‘msg’ и ‘handle’. По ‘msg’ мы проверяем, выполнился ли поток, а по ‘handle’ узнаем его дескриптор. Получив дескриптор, ищем по массиву заданий, к какому урлу он относится и записываем вместо него вожделенное содержимое страницы, получаемое функцией curl_multi_getcontent.
Теперь, когда данный поток выполнил свою задачу, удаляем его из мультикурла, а потом закрываем самого.

После завершения всех потоков функцией curl_multi_close закрываем мультикурл.

Сейчас в массиве заданий $tasks находятся html-коды заданных страниц.

Надеюсь, после прочтения статьи стало немного понятнее, как можно реализовать выполнение “многопоточных” запросов на PHP.

В следующей статье рассмотрим использование stream-функций для подобных целей.

Источник: “Многопоточность” в PHP (curl) / Статьи / Работа для программистов

Рекомендую для прочтения: Уроки PHP. Урок № 11 — CURL


4 комментария для “«Многопоточность» в PHP (curl)”

Оставьте комментарий