Появилась необходимость поднять WebSocket сервер, а так как проет пишется на php, то тут особых вариантов и не было - phpdaemon. Немного погуглив нашел сравнительную статью
Нагрузочное тестирование: Node.JS vs phpDaemon. Результаты порадовали и было решено попробовать это чудо (скажу только, что пока нареканий нет, la упал на порядок + разгрузили fcgi воркеров).
Установка Debian
Установочный образ
debian-6.0.6-amd64-netinst.iso
При установке выбрал: Сервер SSH, Стандартные системные утилиты
Требования phpdaemon
Ставить будем из репо, по-этому необходим git:
# apt-get install git
Новый phpdaemon (от 23 января 2013) требует php 5.4. Поставить можно так (взято с serverfault http://serverfault.com/questions/404815/how-can-i-upgrade-php-to-a-higher-version-for-debian-squeeze):
# echo "deb http://ftp.ru.debian.org/debian/ wheezy main non-free contrib" >> /etc/apt/sources.list
# cat >> /etc/apt/preferences <<EOF
Package: *
Pin: release n=squeeze
Pin-Priority: 650
Package: *
Pin: release n=wheezy
Pin-Priority: -10
EOF
apt-get update
apt-get install -t wheezy php5-cli php5-dev php-pear
Из требований самого phpdaemon:
# apt-get install -t wheezy libevent-dev
# pecl install https://bitbucket.org/osmanov/pecl-event/downloads/event-1.0.0.tgz<
# echo "extension=event.so" > /etc/php5/conf.d/event.ini
# pecl install channel://pecl.php.net/proctitle-0.1.2
# echo "extension=proctitle.so" > /etc/php5/conf.d/proctitle.ini
# cd /usr/src/
# git clone https://github.com/zenovich/runkit.git
# cd runkit/
# pecl install package.xml
# echo "extension=runkit.so" > /etc/php5/conf.d/runkit.ini
# pecl install eio
# echo "extension=eio.so" > /etc/php5/conf.d/eio.ini
Стоит проверить наличие следующих модулей:
# php -m | grep pcntl
# php -m | grep shmop
# php -m | grep sockets
Установка phpdaemon
Репо на github:
https://github.com/kakserpom/phpdaemon
Ставим:
# cd /opt/
# git clone https://github.com/kakserpom/phpdaemon.git
Настройка phpdaemon
Конфиги лежат в
/opt/phpdaemon/conf/ и
/opt/phpdaemon/conf/conf.d/, я предпочитаю их в
/etc/phpdaemon/.
Копируем конфиги:
# mkdir /etc/phpdaemon
# cp /opt/phpdaemon/conf/phpd.conf.example /etc/phpdaemon/phpd.conf
# cp /opt/phpdaemon/conf/AppResolver.php /etc/phpdaemon/
# cp -rp /opt/phpdaemon/conf/conf.d /etc/phpdaemon/
AppResolver.php служит для роутинга приложений (
но! не для WebSocket. для WS ws://server/RouteForAppName).
Правим основной конфиг
/etc/phpdaemon/phpd.conf:
user www-data;
group www-data;
max-workers 1;
min-workers 1;
start-workers 1;
max-idle 0;
# logging-related
logevents 1;
logqueue 1;
logreads 1;
logsignals 1;
verbose 0;
verbosetty 0;
# apps search
appfilepath '{app-*,applications,/var/www/apps-phpdaemon}/%s.php';
# apps routing
path '/etc/phpdaemon/AppResolver.php';
# Pools
Pool:WebSocketServer {
enable 1;
listen '0.0.0.0';
port 8047;
}
Pool:HTTPServer {
enable 1;
listen '0.0.0.0';
port 8080;
expose 1;
}
# Apps
# для фоллбэка на http если клиент не поддерживает websocket
WebSocketOverCOMET {
enable 1;
}
# Для теста доступности по HTTP
Example {
enable 1;
}
# Для теста WS с фолбэком на comet и longpolling
ExampleWebSocket {
enable 1;
}
# прочие конфиги/приложения
#include conf.d/*.conf
Есть один нюанс: пул WebSocketServer должен быть в конфиге до пула HTTPServer, так как на данный момент при инициализации HTTPServer получает инстанс WebSocketServer. Если последнего нет, то он создается, но не пул а отдельный сервер. Если указано несколько воркеров, то только первый сможет сделать бинд для сокета WS.
appfilepath - параметр для поиска приложений. Наши приложения будут лежать в
/var/www/apps-phpdaemon/. Параметр передается в
glob() так:
if (class_exists('Daemon', false) && isset(Daemon::$config->appfilepath->value)) {
$files = glob($g = sprintf(Daemon::$config->appfilepath->value, str_replace('_', DIRECTORY_SEPARATOR, $classname)), GLOB_BRACE);
if (isset($files[0])) {
require $files[0];
return;
}
}
Создадим папку для наших приложений:
# mkdir /var/www/apps-phpdaemon/
Поправим роутер приложений (/etc/phpdaemon/AppResolver.php), чтобы дать доступ к приложению ExampleWebSocket:
- if (preg_match('~^/(WebSocketOverCOMET|Example)/~', $req->attrs->server['DOCUMENT_URI'], $m)) {
+ if (preg_match('~^/(WebSocketOverCOMET|Example|ExampleWebSocket)/~', $req->attrs->server['DOCUMENT_URI'], $m)) {
Тестовый запуск
Запуск:
# php /opt/phpdaemon/bin/phpd start
В файле
phpd в качестве интерпретатора указан
/opt/bin/php, поэтому нужен запуск в виде
php /opt/phpdaemon/bin/phpd.
Проверяем, что все "взлетело":
# ps aux | grep phpd
root 24225 0.5 1.3 95520 6968 ? SNs 14:15 0:00 phpd: master process
www-data 24226 0.3 1.4 96040 7272 ? SN 14:15 0:00 phpd: IPC process
www-data 24227 0.5 1.6 96592 8196 ? SNl 14:15 0:00 phpd: worker process
root 24230 0.0 0.1 7572 868 pts/0 S+ 14:16 0:00 grep phpd
# netstat -ntpa | grep phpd
tcp 0 0 0.0.0.0:8047 0.0.0.0:* LISTEN 24225/phpd: master
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 24225/phpd: master
Если теперь вбить в браузере http://server-address:8080/ExampleWebSocket/ то должны увидеть следующее.
Кнопка Create WebSocket инициирует соединение, Ping - отправляет запро, Close WebSocket - закрывает соединение.
Посмотреть пакеты WS (в хроме) можно щелкнув на соответствующий запрос на установку WS соединения.
Остановить phpdaemon можно так:
# php /opt/phpdaemon/bin/phpd stop
init скрипт (из репо не подходит для squeeze), ротация логов и прочее - на ваше усмотрение.
Постскриптум
Где-то по ходу пропустил зависимость и у меня поставился apache, хотя он и не нужен в данном случае и может только мешать, если HTTPServer phpdaemon будет слушать на 80м порту.