Это вторая статья из серии статей о эмуляции многопоточности в языке 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-функций для решения нашей задачи.

captcha