Неблокирующие сокеты - как с ними работать?
Здравствуйте.
Скажите, пожалуйста, какие способы работы с неблокирующими сокетами существуют в php (т.е. как можно в многопоточном режиме работать).
Я сейчас пишу скрипт для определения google PR и яндекс тИЦ сайтов. Есть список сайтов и для каждого идёт обращение к яндексу и гуглю для определения тИЦ и PR. Если просто последовательно для каждого сайта обращаться к серверу яндекса через fsockopen(), затем к серверу гугля, то выходит очень долго. Соответственно необходимо сделать параллельное обращение к различным серверам с различными запросами.
Слышал, что в php5 что-то добавилось для работы с сокетами, но ничего конкретного не нашёл.
P.S.: английский я знаю плохо, поэтому источники ограничиваются только русскоязычными ресурсами :(
пробуй функции семейства curl_multi_*
Я курл нелюблю :)тебе может помочь curl, проверь, чтобы PHP был с ним скомпилен
пробуй функции семейства curl_multi_*
Насколько я знаю, курл в своей работе использует теже пхпшные функции и ничего нового не добавляет, просто через него удобнее работать. Вы, случайно, не знаете, как в курле реализована данная возможность?
Михаил Харитонов[досье]
Если Вам нужно при каждом обращении к скрипту тянуть с яндекса и гугеля информацию, то посоветовать Вам что-то сложно...
Если же не нужно, то напишите 2 скрипта... один - раз (два, три ии т.д.) в сутки достает информацию и сохраняет, а другой ее только отображает...
простите, если был бесполезен :)
Можно и не делать n-раз в день. Один скрипт пусть будет консольным, там можно пользовать
pcntl_fork. Нафоркал процессов, а они с разных сайтов инфу-то и тянут.
Насчет нелюбви к CURL - это Вы, батенька, напрасно, для Вашего случая самое оно.
PHP-функции CURL не использует, поскольку это отдельная библиотека, писаная на C.
По моему опыту, у multiCURL появляются тормоза, если запрашивать одновременно более 100 урлов, но это можно учитывать и делить массив запросов на пакеты.
Но тут есть еще организационный ньюанс. При многих одновременных запросах можно дозапрашиваться до блокировки ИП.
Дмитрий Донцов[досье], Василий Свиридов[досье] - google PR и яндекс тИЦ, возможно, обновляются не чаще раза в день, точно не знаю, но сильно много даже им работенки несколько раз в день пересчитывать такие вещи.
Михаил Харитонов[досье], пишите в личку, скину библиотеку, которую я сделал для работы с неблокирующими сокетами.
Можно писать как клиенты, так и сервера.
Используется PHP5 и socket streams.
Можно использовать и обычные socket_*, но преимущество streams в том, что вы можете прозрачно работать с HTTPS.
google PR и яндекс тИЦ, возможно, обновляются не чаще раза в день, точно не знаю, но сильно много даже им работенки несколько раз в день пересчитывать такие вещи.
На самом деле тИЦ пересчитывается раз в 2 недели, а PR - раз в 3 месяца. Вот только точных дат никто не знает, поэтому и приходится каждый день проверять. Но у меня немного другая задача. Я пишу скрипт для определения конкурентности запроса. Т.е. скрипту скармливается запрос, по этому запросу выдирается, скажем, первые 10 сайтов из результатов поиска яндекса и для каждого из найденных сайтов определяется тИЦ и PR, потом для каждого из этих сайтов определяются (опять через запрос к яндексу и парсинка результатов поиска) сколько существует ссылок с других сайтов на этот сайт (или страницу) с текстом запроса в ссылке и т.д.
Т.е. нет единого списка сайтов. Поэтому и нужно максимально ускорить скорость работы при обращении через сокеты.
Михаил Харитонов[досье], Ваша задача, как мне кажется, состоит вовсе не в том, чтобы "используя сокеты, как можно быстрее скачивать данные с сайтов".
Тут надо смотреть в сторону многопоточности и параллельной обработки данных (звучит страшно, а так - просто). Т.е. переработать алгоритм так, чтобы, скажем, для каждого сайта он выполнялся независимо или слабозависимо от результатов других сайтов, и запускать по сайтам параллельно в несколько нитей, или потоков - как получится удобнее. И сокеты здесь вовсе ни при чем.
Дмитрий Шер aka sherd[досье], уважаемый, позвольте с вами не согласиться.
В этой задаче как раз нет нужды смотреть в сторону многопоточности и всяких форков.
Нужно: послать множество запросов, получить ответ и сделать несложные операции (записать в БД, например).
Форканье процессов приводит к ненужному потреблению памяти.
Собственно, большую часть времени скрипт будет ожидать ответа сервера, соотвественно при использовании форка n процессов будут висеть в памяти и ждать.
Зачем же запускать много процессов, если это можно сделать из одного?
Для этого и существуют асинхронные операции с сокетами.
Вы коннектитесь или посылаете данные и не ждете завершения системного вызова.
Записались данные во время системного вызова - хорошо, нет - можно подождать, пока они не запишутся, а в это время обработать другие данные.
Для этого есть волшебная функция select.
Кстати, тот же squid и еще множество других приложений используют неблокирующие сокеты.
А использование форка, имхо, оправдано в том случае, если идет интенсивная нагрузка на процессор при многопроцессорной трататайке, либо по каким-то причинам нельзя обрабатывать несколько клиентов в одном процессе, а нужно обеспечить им разные адресные пространства.
Для задачи Михаила я вижу 2 варианта: либо использование multi curl, чего автор не хочет, либо непосредственная работа с сокетами.
Использование неблокирующих сокетов экономит память, но немного усложняет логику приложения, надо более корректно обрабатывать ошибки: если процесс аварийно завершается, то накрывает все соединения.
Как и везде свои плюсы, свои минусы.
И, конечно, работа с сокетами доставит вам массу впечатлений, можете поверить. :)
Однако, оно того стоит.
А в CURL да, есть то, что в PHP не реализуется (функции stream_* в расчет не беру, не работал, но по-любому их применение посложнее CURL-а будет).
Это соединение через прокси, установка USERAGENT и REFERER, информация о ошибках соединения и т.д.....
Это соединение через прокси, установка USERAGENT и REFERER, информация о ошибках соединения и т.д.....Но соединение через прокси, установка UserAgent'а и т.п. - это всё можно вообще при помощи fsockopen() и HTTP запроса реазизовать. Подключение через прокси к удалённому HTTP серверу я сам много раз реализовывал...
Да, Дмитрий Кононов[досье] все правильно сказал - вам просто нужна асинхронная работа. Не в скорости скачивания или работы с самим сокетом дело, а с алгоритмом.. либо нитки, либо асинхронные сокеты, либо потоки. Выбирайте )Да я, вообще то, перед созданием темы ещё определился :)
Мне нужны асинхронные сокета. Но как с ними работать в php (и вообще) я не знаю. Поэтому и прошу подкинуть ссылки (не php мануал), где моно почитать об них или примеры работы с ними. И скажите, пожалуйста, неграмотному человеку (т.е. мне), чем socket_* ф-и отличиются от stream_socket_* ф-й в php?
Михаил Харитонов[досье], ну что ж вы никак не успокоитесь. :)
Я вам скинул библиотеку, используйте ее, ничего писать не нужно.
Просто посмотрите пример использования в examples/get.php и удалите лишние обработчики.
Что касается описаний на русском, то я их встречал крайне мало.
Основная масса на английском.
Если хотите научиться работать с сокетами, то все рекомендуют книги Стивенса:
- W. Richard Stevens Unix Network programming, volume 1.
- W. Richard Stevens TCP/IP Illustrated, volume 1.
- W. Richard Stevens, TCP/IP Illustrated, volume 2.
Правда, я их не читал. :)
Вторая книга у меня есть в pdf (на английском). Если надо - пишите.
Вот переводное издание:
http://www.piter.com/book/978594723991/
Один товарищ пишет про неблокирующий коннект по-русски:
http://www.kalinin.ru/programming/network/01_12_00.shtml
Теперь про отличия функций socket_* от stream_socket_*.
Как пишут в документации, "The socket extension implements a low-level interface to the socket communication functions", то есть функции socket_* реализуют низкоуровневый интерфейс к функциям сокетов. Если посмотреть исходный код этого расширения, то можно увидеть, что данные функции являются просто оберткой для Си-шных функций:
socket_connect -> connect
socket_bind -> bind
socket_read -> recv
и т.д., которые в свою очередь делают системные вызовы ядра.
Используя это расширения, вы пишете так же, как и на обычном Си.
Здесь есть очень полезная функция socket_get_option (в Си getsockopt), которая позволяет получить значения многих параметров.
Функции же stream_socket_* - это часть интерфейса streams.
Streams, или потоки, являются обощенным интерфейсом для доступа к различным ресурсам: локальным файлам, протоколам HTTP/HTTPS, FTP/FTPS, даже SSH и т.д. Вы даже можете делать свои собственные потоки.
Делая обычный fopen, вы сами того не подозревая открываете поток.
Например, вот такой вызов
$fd = fopen("/path/to/file", "r");
открывает локальный файл.
Вот такой:
$fd = fopen("http://www.example.com/", "r");
уже открывает страничку на удаленном сервере.
Работа с потоками унифицирована: для чтения/записи вы используете обычные fread/fwrite.
Можно определить свой префикс, например, "mydb://" и извлекать из базы данных какой-нибудь блоб.
Функции stream_socket_* предназначены для работы с сокетами в рамках streams.
Они в какой-то мере упрощают работу программиста, не нужно делать лишние вызовы.
Например, чтобы создать сервер с использованием sockets, нужно было делать несколько вызовов:
// creating, binding and listeninig socket
if (!($s_listen = socket_create(AF_INET, SOCK_STREAM, 0)) ||
!socket_set_option($s_listen, SOL_SOCKET, SO_REUSEADDR, 1) ||
!socket_set_option($s_listen, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>5, "usec"=>0)) ||
!socket_set_option($s_listen, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>5, "usec"=>0)) ||
!socket_set_nonblock($s_listen) ||
!socket_bind($s_listen, $config["listen_addr"], $config["listen_port"]) ||
!socket_listen($s_listen)
)
sock_err(null, true);
В streams кода поменьше:
// creating, binding and listeninig socket
if (!($s_listen = stream_socket_server("tcp://{$config['listen_addr']}:{$config['listen_port']}", $errno, $errstr)))
sock_err(null, true);
То есть bind и listen делаются одним вызовом.
Также можно легко и непринужденно забирать странички по протоколу HTTPS.
Правда, здесь уже нет аналога функций socket_get_option/socket_set_option, поэтому вы не сможете управлять сокетом столь тонко, как это делается в sockets.
(Я не совсем понимаю, почему разработчики не предусмотрели эту возможность.)
Кроме того, streams появились позднее, в баг-репортах было много ошибок.
Возможно, они не все еще пофиксены. Например, я одну так и не смог побороть, не понимаю, бага это или фича. :) Тем не менее, обошел ее.
socket_* и stream_socket_* по сути делают одно и то же, но никак не соотносятся между собой.
То есть вы не сможете использовать функции socket_* в streams и наоборот.
А вообще не стоит сильно углубляться в работу с сетью, если вам это не сильно нужно.
Потеряете много времени. Используйте готовые решения.
я сейчас также ищу интсрументы для асинхронной работы, для многопоточного(канального,сессионного или еще как:) ) вебклиента, мне нужно
одноврменно от 1000 сессий открывать, попробовал POE на перле, жрет много памяти и непонятно куда она уходит и как освобождать...
не делали ли вы часом тестов сколько памяти кущается при параллельном получении содержания 1000 урлов, и освобождается ли она после завершения коннекта ?
и вообще, нет ли каких то глюков при открытии более 1000 ас. сокетов на пхп ?
Cook[досье]
На таких объемах не тестировал. :)
Да и зачем столько много?
Сделайте пакетную обработку по 20-30-40 соединений.
А 1000 одновременных коннектов хостер может не одобрить, приравнять это к вредоносной деятельности...
Вы получаете одновременно 1000 страничек и храните их в памяти, каждая, скажем, килобайт по 20 — вот вам и получается 20 метров памяти минимум. :)
Если полученные блоки сразу же сохранять на диск, тогда надо держать открытыми порядка 1000 дескрипторов. Тоже определенные накладные расходы.
Вообще, по освобождению памяти в PHP5 сделали много улучшений.
По моим тестам она освобождается достаточно быстро, не растет лавинообразно.
Рискну разместить тут ссылку на свою библиотеку (да простят меня модераторы):
http://inetserv.sourceforge.net/
Попробуйте ваши 1000 коннектов, отпишитесь потом, как работает.
![[logo]](/site/images/logo.jpg)
