Как стать автором
Обновить

Комментарии 47

красиво реализовано, взял на заметку, спасибо!
if (count($this->sockets) == ) {

тут закралась опечатка или это новая магия? :)
Спасибо, сейчас подправлю. Скорее всего при подсветке побилось, конечно же if (count($this->sockets) == 0) {
Нет все таки этот парсер хабра неправильно обрабатывал тег font, убрал выделение… Мистика…
А CURL не подходит для таких задач?
Ну curl не подходит если надо постоянно держать некоторое число запросов запущенными. То есть пока не выполниться вся группа запросов, нельзя запустить ещё один, ну или приодеться использовать костыли.
Вот это пробовали curl_multi_init (http://us3.php.net/manual/en/function.curl-multi-init.php)?
Я знаю как работает curl_multi_init. Как на нем реализовать подобный код?

for ($i=0;$i<5;$i++) {
    $async->get('http://example.net/cluster.php');
}

while (($threads = $async->iteration()) !== false) {
    foreach ($threads as $id) {
        $async->get('http://example.net/cluster.php');
    }
}

То есть сначала запускаем 5 потоков, как только завершается один из них запускаем следующий, таким образом все время будет запущенно 5 потоков. Я не видел подобных примеров на curl, там запросы запускаются группами то есть сначала 5 запросов, потом ещё 5 запросов и т. д.
Вы определённо не правы.
Как раз таки это CURL умеет, я как-то раз реализовывал подобную штуку.

См. в xpart.ru/resume/sources.zip исходник «Search Engines Multithread Parser\classes\class.multipager.php»
мультикурл нестабилен и жрет много ресурсов.
Лично я ничего такого не наблюдал. Можете подробнее рассказать?
Ну есть там засада с потреблением памяти. Я так понимаю, интерфейс построен так, что приходится выделить большой блок памяти заранее. Обходится легко. Вот в библиотеке code.google.com/p/multicurl-library/ даже в примере это сделано.
Cсылка на либу MultiCURL вот: code.google.com/p/multicurl-library/downloads/list

Вот небольшое описание на русском языке, с примером: www.weblancer.net/users/tvv/portfolio/231798.html

Примечание к описанию: wait() вызывается в самом конце, скачивание начинается сразу при первом же addUrl(). Cкачивание c каждого ресурса происходит абсолютно независимо друг от других ресурсов, для каждой скачки можно истанавливать свои параметры (например, разные прокси, разные параметры авторизации, разные протоколы, включая HTTPS, разные таймауты и т.д.). Параллельно скачиванию можно выполнять любые другие действия.

Про память — таки да, неконтролируемо жрет-с. Это недостаток этого решения. Как побороть пока не придумал. Хотя для большинства проектов, где данная либа используется, это не критично.
> мультикурл нестабилен и жрет много ресурсов.

«нестабилен» — это не так, весьма стабилен (но в некоторых старых версиях бывают утечки памяти в некоторых случаях! так что всегда старайтесь использовать последние версии)

«жрет много ресурсов» — да, согласен, что есть то есть. это плата за универсальность и за следование стандартам, и это стоит того. хотя в любом случае, слово «много» — это понятие относительное, и не думаю что подобное решение в лоб, реализованное на PHP (с поддержкой разных протоколов, проксей, кукисов, таймаутов, редиректов и т.д.), будет потреблять меньше ресурсов.
вообще можно, вот костыли начнутся с получением кода ошибки и ее текста bugs.php.net/bug.php?id=48304
Кстати да, чем не CURL?
С этими сокетами все хорошо до одного момента, пока не наткнетесь на проблему «получать статус сокета»:
например, сервер вдруг затупил и не выдает ответа, в буфере ничего нету, но вы циклически его читаете и думаете что все вычитали, хотя надо просто ждать, так как данные будут чуть позже.

И что самое обидное, get_socket_status (которая могла бы помочь) с сокетами socket_create() работать не будет (точнее она не скажет eof для неблокируемого сокета)! Ей подавай fsockopen-сокеты только.

Так что ваша реализация хороша, пока сервера отвечают быстро.
Мне нужно было достучаться для своего же сервера просто на другие IP, так что в моем случае этого достаточно, но ничто не мешает добавить для каждого сокета время запуска и проверять в цикле его на таймаут.
Согласен.
Правда, если все-таки хоть какие-то данные надо вычитывать, то таймаут, ИМХО, не самое правильное решение проблемы будет.
Просто надо писать с использованием stream_select().
Xexe :) Когда автор столкнется с нестабильными и периодически неработающими серверами — начет рварь волосы на голове, но надеюсь доведер реалиализацию до ума. Ни в коем случае не хочу обидеть — просто когда-то давно, еще во времена пхп4 стояла задача написания некоторого бота для обработки 200-300к доменов в сутки. Сами понимаете — никто не мог гарантировать работоспособность хотябы 10% из них, поэтому начались нескольконочные танцы с бубнами вокруг таймаутов (которых у автора вообще не наблюдается) и обманыванием пхп — через локальное обращение к самому себе чтобы запустить очередной пхп процесс (т.е. тупо 5-10 процессов обслуживали стабильно с жесткими таймаутами 300-400 соединений единовременно)

Автору предлагаю остановится тогда когда получится стабильно забивать свой канал под 99%, с минимальными затыками (3-5 сек в минуту) аааа себе предлагаю порытся в старых исходниках и написать либу под ZF заодно портировав под пхп5, со стрим_селект работать вроде поудобнее будет
ааа и ну конечно в идеале держатся подальше от pctl который много где не стоит и следить за использованием ресурсов. У меня использовался только канал, процессор и память никак напрягались.
я тоже доставлял себя много анальной боли костылями, плюясь на отвратный курл, плохие доки, неведомые глюки с таймаутами и прочую кривоту, потом просто взял эрланг и за вечер набросал отлично контролируемый кравлер со всеми таймаутами и прочим. работает быстрее, надежнее и гибче курла.
Хороший велосипед — будем ездить! Спасибо!
пока вы пишете велосипеды, в мире перл уже давно все написано
LWP::Parallel
а заминусовали видимо от зависти, что в мире Perl есть такое централизованная система обмена полезным кодом, в которой хорошим тоном считается снабдить модуль документацией и тестами (которые к слову запускаются в автоматическом режиме на куче разных платформ чтобы уведомить автора о потенциальных проблемах) и также принимать багрепорты.
Вы не поверите, но в мире php это тоже есть =)
LWP — мощный инструмент, знаю не по наслышке, но речь не о нем, а ваш комментарий немного разошелся с темой обсуждения и провоцирует к холиварам. Реакция на лицо
Eсть, но не очень впечатляет, к сожалению (если вы про pear/pecl) :(

Просто выкладывать очередной написанный класс на хабр и говорить вот мол написал как-то так — потом получается что проект состоит из «скопировал с хабра», «скопировал с какого-нибудь еще сайта».

На мой взгляд интерфейс у класса слегка страдает, отсутствует возможность настройки какой-либо.

Почему бы автору не оформить это в законченный продукт — сделать минимальную документацию — выложить на github, google code, на тот же pear.
При этом можно посмотреть есть ли какая-нибудь уже хорошая библиотека для отправки http/ftp запросов и нельзя ли использовать к примеру threads c ней.
улыбнуло в комментах кода — «На всякий случай»
на какой такой всякий случай? (с) =)))
На случай если попытка чтения из сокета не вызовет задержки и цикл будет работать в холостую, поэтому там стоит небольшая пауза.
дело в том, что задержка будет всегда в случае, если сервер медленный или расположен далеко
как написали выше, это может стать проблемой
Именно по этому «На всякий случай» :)
Да хорошее, решение. Скорее всего оно бы мне подошло. Спасибо буду иметь в виду.
Мне тоже понравилось, мне как раз в текущем проекте оно пригодилось. Рад что помог.
Хорошее решение, проверю на очень медленных соединениях. Спасибо.
> $socket = socket_create(AF_INET, SOCK_STREAM, );

вот тут опечатка, скорее всего побилось в парсере, наверное надо дописать SOL_TCP как третий параметр

я бы на Вашем месте изменил бы имена get, ну и post за ним. Лично для меня get означает что-то получить из класса, а не метод запроса для хттп. Возможно это просто дело привычки, но мне кажется более уместными были бы имена getRequest, postRequest или вроде того.
Если бы это было написано на С с BSD-сокетами, я бы сказал что это пример того, как делать не надо.
Может быть на PHP так принято?

// Если установить флаг до socket_connect соединения не происходит

Скорее происходит, но не сразу. А сразу возвращается ошибка, потому что операция потенциально блокирующая.

socket_set_nonblock($socket);
socket_write($socket, $method." ".$parts['path']." HTTP/1.1\r\n");
socket_write($socket, «Host: ».$parts['host']."\r\n");
socket_write($socket, «Connection: close\r\n»);
if ($data) {
socket_write($socket, «Content-Type: application/x-www-form-urlencoded\r\n»);
socket_write($socket, «Content-length: ».strlen($data)."\r\n");
socket_write($socket, "\r\n");
socket_write($socket, $data."\r\n");
}
socket_write($socket, "\r\n");

Ни одной проверки на то, что socket_write записал все, что его просили записать. Он и в блокирующем-то режиме судя по документации может записать не всё, а в неблокирующем тем более.

$data = socket_read($socket, 0xffff);
if ($data) {
$threads[] = $key;
$this->setThread($key, $data);
unset($this->sockets[$key]);
continue;

Опять же, никакой проверки длины. Наверно это будет работать для мелких ответов. Но по чистой случайности.

// На всякий случай
usleep(5);

Воспользуйтесь socket_select, чтобы отсеять большую часть «всяких» случаев.
Не гуглить — здоровью вредить.
code.google.com/p/multicurl-library/
этот.парент комментарий к комментарию pwlnw выше
Тот велосипед значительно практичней чистых функций: там есть очередь и регулируется число одновременных соединений.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
тогда будет уже две проблемы: написать обертку вокруг libev и написать программу на php, которая будет использовать эту обертку. :)
НЛО прилетело и опубликовало эту надпись здесь
2009 =). По вашей статье разберусь с сокетами.

Никогда не поздно — Спасибо!
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории