четверг, 1 июля 2010 г.

PHP socket_select - не подходит для написания сетевых приложений

UPD В первой редакции статьи я ошибочно писал про stream_select. Статья отредактирована подобающим образом.

Обнаружил очень забавное поведение у функции socket_select - она не возвращают информацию об изменениях, если в буфере чтения уже лежат какие-то данные. Получается просто зацикливание приложения на socket_select. Такого поведения в принципе легко достичь, если медленно обрабатывать большой пакет данных.

Алгоритм до безобразия прост.

Шаг 1. socket_select рапортует, что данные в буфере. Вычитываешь их и обрабатываешь.

Шаг 2. Вычитываешь и обрабатываешь предпоследний пакет данных. И пока ты его обрабатываешь в буфер приходит недостающий последний пакет.

Шаг 3. Ты вызываешь socket_select и ждёшь данные. Так как данные уже пришли, socket_select тебе об этом не говорит. Получается, что в буфере уже лежат нужные тебе данные, но не предоставляется никакого механизма узнать об этом. Потому ты будешь ждать ответа пока данные снова не придут в этот сокет. А они могут вообще не придти, если протокол последовательный.

С неблокирующими сокетами - та же проблема. Прежде чем передать сокеты в socket_select надо проверить все буферы чтения. А пока ты их проверяешь, данные могут придти и улечься в буфер чтения. Снова ты их потеряешь.

Выводы:
socket_select в общем случае не подходит для задачи написания сетевого приложения. В общем случае надо использовать работу с неблокирующими сокетами и перебор сокетов. Обнаружил, что недавно зарелизилась libevent для PHP надо будет попробовать инструмент в действии. В конце концов она работает не на селектах, а на epoll и kqueue.

UPD Для написания сетевого приложения можно использовать stream_select, она лишена указанного недостатка socket_select

PS. Спасибо Clanth

5 комментариев:

Clanth комментирует...

Попробовал смоделировать такую ситуацию,
код сервра:

$sock = stream_socket_server('tcp://127.0.0.1:11102');
$newsock = stream_socket_accept($sock);
stream_set_blocking($newsock,0);
sleep(10);
$Read = array($newsock);
$Write = $Except = null;

echo 'select...';
$res = stream_select($Read,$Write,$Except,null);
$r = fread($newsock,1024);
echo $r;

sleep(10);
echo 'select...';
$res = stream_select($Read,$Write,$Except,null);
$r = fread($newsock,1024);

echo $r;

fclose($newsock);
fclose($sock);


клинета:

$sock = stream_socket_client('tcp://127.0.0.1:11102');

stream_set_write_buffer($sock,0);
echo 'send 1';
stream_socket_sendto($sock,"test");
sleep(15);
echo 'send 2';
stream_socket_sendto($sock,"test");


sleep(100);
fclose($sock);



Тоесть идет такая последовательность:
1. подключение
2. отправка вообщения №1
3. вызов select
4. считывание сообщения
5. отправка вообщения №2
6. вызов select

все отлично отработало

Alexxz комментирует...

Мдя, со stream_select я не проверял, а понадеялся на фразу из мана "Its operation is equivalent to that of the socket_select() function except in that it acts on streams."

Проверял я с sock_select. А вот комментария подтверждающий мои слова про sock_select http://ru2.php.net/manual/en/function.socket-select.php#98222

Статью исправлю

Clanth комментирует...

Пару месяцев назад выбирал чем пользоваться и натыкался на какие-то проблемы с socket_* функциями, и хотя предпочитаю работать с более низким уровнем, пришлось использовать stream. А где-то читал даже что в socket то ли не исправляют ошибки, то ли сами разработчики рекомендуют не использовать и удивлялись почему не поставят раз так флаг deprecated

а libevent пока нестабильностью (хоть и бета) себя не показала, впрочем по скорости (по крайней мере до 1000 активных соединений) разница неощутима

danilk комментирует...

добрый день. в данные момент пишу серевное приложение на сокетах и столкнулся с тем, что при 20 коннектах и относительно небольшом количестве данных, сокет начинает медленно читать данные от клиентов, приложение "зависает" и в логгере меееедленно идут овтеты клиентам. сбрасываю до 10 клиентов - все окей. пробовал через socket_select, решения Net_SERVER, stream_socket_server - ни в какую. не сталкивались с такой проблемой?

Анонимный комментирует...

2Alexxz
была та же проблема, select говорил мол "нечего там читать"... попробовал сделать сокет не блокирующим( socket_set_nonblock ) до вызова select... и что Вы думаете? ;) - select сказал что данные есть))
хотелось бы узнать теперь почему это так...