Pull to refresh

Comments 25

Жаль не на PHP6…
А если серьезно, то и на 5-й версии работать не будет. 5.3+ как минимум после беглого взгляда.
Код-стайл никакой не применяется, весь код написан под влиянием сиюминутной страсти. PSR, Composer — нет, не слышал. Минимальный тайп-хинтинг хотя бы на до-php7 уровне — лишнее. Велосипедам — да! Нести 20 смысловых строчек на хабр — да!
Думаю сейчас будет много холиваров… Но демоны на пхп это всегда палка о двух концах. Но в данном случае чем больше попыток и вариантов, тем лучше. Автор молодец, доводи до красоты.
Первая мысль у меня была такая же. А потом я подумал, что ушел с php 5 лет назад, с тех пор многое могло измениться. Назад на этот язык не тянет, но вполне возможно то, что раньше было актуально сейчас уже просто миф?
На что пересели, если не секрет?)
Как было сказано выше, для начала работы нашего демона, в соответствии с правилами работы подобных программ в Linux, его нужно отвязать от терминала, в котором его запустили для этого нужно воспользоваться функцией pcntl_fork(), она создаёт дочернюю копию текущего процесса и возвращает его числовой id в случае успеха. Ну и конечно прикончить родительский процесс.

Все это вовсе не обязательно.
Достаточно запустить скрипт в фоновом режиме с использованием nohup:
% nohup your_script.php &

В дебиане и убунту есть специальный скрипт для запуска любой программы в виде демона: help.ubuntu.ru/wiki/start-stop-daemon

А можно вообще не перенаправлять ничего. Просто запускаем скрипт в сессии screen.

Таким образом, операционная система будет знать что мы способны определять своё поведение и поместит pid нашего процесса в очередь для получения системных сигналов.

В какую еще очередь на получение системных сигналов? Нет никакой такой «очереди».

setsid() делает процесс лидером группы и отвязывает его от терминала (т.е. процессу перестают посылать сигнал SIGHUP при закрытии терминала).

Приступим, для начала необходимо определиться что что класс должен уметь делать.

Получать и обрабатывать сигналы операционной системы;
Уметь понимать запущен ли демон или нет;
Запускать задачу необходимую для демонизации;
Знать когда нужно остановиться;

Какой чудесный класс. Авто-, мото-, вело-, фото- гонщик, зритель и пловец.

Задачи управления демоном (запуск, снятие, проверка пид-файла) практически всегда выполняются внешними скриптами, лежащими в /etc/init.d. И раз уж вы пишете своего собственного демона, то без этих скриптов вам все равно не обойтись.
Обработка сигналов и собственно выполнение работы — это задача самого демона.

Здесь мы проверяем все возможные варианты событий, существует ли файл, если да то каким процессом создан, проверяем существует ли такой процесс, если процесс создавший файл не существует, так как процесс завершился неожиданно, пробуем удалить файл

Отнюдь не все возможные. Например, если в файл записать число 1 (пид процесса init), то ваш демон не запустится никогда.
Впрочем, это ладно, это стандартная практика.
А вот выходить без диагностики, если не удалось удалить файл — это как-то очень нехорошо. Собственно, а зачем вы вообще его удаляете? Ведь при несчастливом стечении обстоятельств может получиться так, что вторая копия вашего демона «успеет» запуститься как раз тогда, когда файл удален, но новый файл еще не создан. В результате обе копии решат, что они одни-единственные на свете, обе счастливо запишут свой пид в файл (одна поверх другой), и начнут оттаптывать друг другу пальцы.

Еще у вас нет никаких блокировок при записи пид-файла. Вы уверены, что file_put_contents() запишет все в файл атомарно, а file_get_contents() его так же атомарно прочтет? Я вот — нет.

// Собственно создаём демона, соответственно говорим ему куда записывать свой pid…
$daemon = new Daemon('/tmp/daemon.pid');

// Закрываем порочные связи со стандартным вводом-выводом…
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);

// Перенаправляем ввод-вывод туда куда нам надо или не надо…
$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen('/dev/null', 'wb');
$STDERR = fopen('/dev/null', 'wb');

Как я уже говорил, все это практически не нужно. Но если уж так чешутся руки написать свой демонайзер на РНР, то делайте это правильно:

1. Вместо fclose()/fopen() нужно использовать функцию dup2(), чтобы номера файловых дескрипторов остались гарантированно те же, что и при старте программы. На С это выглядит так:
int fd = open("/dev/null", O_RDWR, 0);
if (fd != -1) {
   dup2(fd, STDIN_FILENO);
   dup2(fd, STDOUT_FILENO);
   dup2(fd, STDERR_FILENO);
   if (fd > 2) close(fd);
}


2. После всех прыжков с файловыми дескрипторами, терминальными сессиями и пид-файлами остался один, но очень важный шаг: сделать chdir("/").
Смысл этого шага — разблокировать текущий каталог после запуска демона. Скажем, если зайти на подмонтированную флэшку, а потом запустить вашего демона, лежащего, например, в /usr/bin, то флэшку после этого отмонтировать не получится: она останется в качестве текущего рабочего каталога у запущенного демона.

И да, с запятыми вам надо что-то делать. Примерно в половине фраз отсутствуют.
В php dup2, к сожалению, никак не прокинут.

А вообще, конечно, написание классических демонов в 2015-м году выглядит странно. :)
Но зачем?
Есть же celery и подобные штуки. php даже умеет с ними работать…
> поместит pid нашего процесса в очередь для получения системных сигналов

А простите, как можно сделать так, что бы пид не получал системные сигналы?
скрипты стартующие с помощью cron каждую секунду (да, да видел и такое)

У крона минимальный интервал — одна минута, а не одна секунда. Если используются какие-то хаки, вроде запуска 60-ти экземпляров скрипта раз в минуту с инкрементным sleep, то это не значит, что скрипты стартуются с помощью крона каждую секунду.
можно 60 строчек со sleep написать
Я как раз об этом и писал:

* * * * * /path/to/command # экземпляр 1
* * * * * sleep 1 && /path/to/command # экземпляр 2
* * * * * sleep 2 && /path/to/command # экземпляр 3
# . . .
* * * * * sleep 58 && /path/to/command # экземпляр 59
* * * * * sleep 59 && /path/to/command # экземпляр 60
Кроны бывают разные. У fcron, например, минимум 20с
Сейчас, с современными init-системами, делать самофоркающиеся демоны — это моветон. Гораздо проще написать программу, которая работает как обычно и пишет логи в stdout. После этого написать юнит для systemd или что-то аналогичное — и все «фичи» демона становятся доступны автоматически.
Видимо, там просто старый CentOS. Хотя и в этом случае можно установить тот же runit.

А так — конечно же. Это намного проще и гибче в управлении.
https://fedoraproject.org/wiki/User:Johannbg/QA/Systemd/Daemon#New-Style_Daemons
Тысяча статей уже написана на эту тему. Да и библиотеки есть давно позволяющее сделать это на более высоком уровне. В гугле вариантов масса. К чему эта статья?
Простите за наивный вопрос, но для какого типа задач требуется запускать подобного демона?
Могу сказать из своего опыта, что обычно такие демоны нужны для разбора отложенных и «тяжелых» задач, то есть тех задач которые не могут быть выполнены в apache/fpm так как может не хватить времени, ресурсов (читай памяти) или воркеров сервера.

Как это происходит. Когда какой-то web запрос решает что надо сделать «тяжелую» задачу, он отправляет ее в очередь и завершает запрос (обычно выводят что-то вроде сообщения «ваш запрос обрабатывается» или прогресс бар и через ajax проверяют статус выполнения задачи что бы сообщить о результатах на странице). Созданный демон разбирает эту очередь в фоне и выполняет «тяжелые» задачи. Что бы успешно разбирать много задач одновременно, как правило, демон делают много процессорным (используя pcntl_fork()), каждый дочерний процесс выполняет свою отдельную задачу.

Конечно это можно сделать и кроном, но тут не будет эффекта real-time, так как разрыв между запуском будет N секунд (обычно 1 минута). За это время может накопиться куча задач, а так же заставлять пользователя ждать — плохая практика.
ок. минус этого сценария в том, что демон запускается из скриптов. для запуска нужно раздать права на такое мероприятие и сам процесс будет жрать много ресурсов: php прожорлив. Плюсом всплывает проблема, что это будет «не тот php», что крутится в апаче — конфиги нужно цеплять.

Получается дешевле во всех планах (отладка, использование ресурсов, ручной запуск и пр.) делать то же самое, но в bash и так же запускать демоном.

Единственное, что неудобное в bash это работа со строками.
Работа с БД/redis/итд в bash? Вы странный :)
Если демон имеет одну кодовую базу и логику с проектом (а так и стоит делать), то функциональность демона, его расширение и поддержка не становится проблемой
Ну обращение к базе, это не самое страшное и легко решаемое. Грамотно составленный и отлаженный SQL-скрипт решает эти проблемы. Причем явный выиигрыш в скорости.

Проблемы могут всплыть прямо на отладке работы этого демона. Мне вот интересно, у тех, кто это уже попробовал использовать: с какими правами и от какого пользователя сначала запускается демон, а потом выполняется сам php-скрипт.

9 месяцев назад сталкивался с такой проблемой. Задача следующего типа: дома есть «домашний сервер» в виде распберри. Есть так же доступная по локальной сети камера наблюдения. В целях безопасности не стал использовать сервис производителя камеры для доступа и записи данных (по понятным каждому причинам. Что поделать — после капырнадцати лет прожитых в россии я стал параноиком). Вместо этого организовал следующюю цепочку: камера при срабатывании датчика движения обращается к определенному адресу на сервере и записывает фотки на локальный sftp. Серверный php-скрипт уже в свою очередь запускает башевский скрипт, в котором стартует screen, в котором исполняется сохранение этих фоток на внешее хранилище (на самом деле не все так просто: внешее хранищлище нужно сначала включить), параллельно отсылает на почту по ранее указаным адресам и через систему роутера делает звонок на мой мобильный с домашнего номера.

Половина всех операций реализована в виде «услуги» на домашнем сервере, другая часть еще нет, но желание внести есть. Плюс еще сразу делаю с api через php. обмен нестандартными сервисами типа SOAP или других -напомю, я параноик. И изобретаю свой велосипед. Но дело в том, что мне так же нужно прозрачное и удобное управление через консоль.

Использование screen напроч снимает проблему с демонами. Например на домашнем сервере нужно запустить minidlna. Причем можно с разными режимами — только просмотр фото, только просмотр видео(развлекаловка) или просмотр видео из семейного архива — или же комбинации этого.

Точно такая же проблема стоит при получении новых фото и видео семейного архива — реплицирую сразу на два носителя. В наш век один ненадежен. Но при запуске через screen есть возможность войти в этот процесс консолъно и контролировать как он идет. С демонами такое уже не возможно.
Для этих целей идеально подойдет расширение github.com/php-ion/php-ion, но оно еще, официально, не зарелизилось и только под PHP7
Only those users with full accounts are able to leave comments. Log in, please.

Please pay attention