четверг, 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