diff --git a/Command/StartCommand.php b/Command/StartCommand.php new file mode 100644 index 0000000..9994d6a --- /dev/null +++ b/Command/StartCommand.php @@ -0,0 +1,81 @@ +setName('websocket:start') + ->setDescription('Start websocket server daemon') + ->addArgument( + 'server_name', + InputArgument::OPTIONAL, + 'The server name (from your varspool_websocket configuration)', + 'default' + ); + } + + /** + * @see Symfony\Component\Console\Command.Command::execute() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + + $name = $input->getArgument('server_name'); + parent::setConfName($name); + + if (parent::checkPid()) { + $output->writeln('PID file already exists. Maybe the server is running?'); + return; + } + + // fork process for demonizing + $pid = pcntl_fork(); + + if ($pid < 0) { + $output->writeln('Unable to start the server process'); + return 1; + } + + if ($pid > 0) { + $output->writeln(sprintf('%s WebSocket server is running', $name)); + // stop parent process + return; + } + + if (posix_setsid() < 0) { + $output->writeln('Unable to make a process as session leader'); + return 1; + } + + $manager = $this->getContainer()->get('varspool_websocket.server_manager'); + + // use Symfony's Monolog for logging + $logger = $this->getContainer()->get('logger'); + $manager->setLogger(function ($message, $level) use ($logger) { + $logger->log( $level, $message ); + }); + + // create pid file and handle system signals + parent::createPid(); + parent::handleSignal(); + + // use parent::listenPid() method as callable to shutdown the server + $server = $manager->getServer($name); + $server->run(array('Varspool\WebsocketBundle\Command\WebsocketCommand', 'listenPid')); + } +} +?> \ No newline at end of file diff --git a/Command/StopCommand.php b/Command/StopCommand.php new file mode 100644 index 0000000..d44655f --- /dev/null +++ b/Command/StopCommand.php @@ -0,0 +1,53 @@ +setName('websocket:stop') + ->setDescription('Stop websocket server') + ->addArgument( + 'server_name', + InputArgument::OPTIONAL, + 'The server name (from your varspool_websocket configuration)', + 'default' + ); + } + + /** + * @see Symfony\Component\Console\Command.Command::execute() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + + $name = $input->getArgument('server_name'); + parent::setConfName($name); + + if (!parent::checkPid()) { + $output->writeln("PID file doesn't exist. Maybe the server isn't running?"); + return; + } + + $pid = parent::getPid(); + + // send sigterm signal to shutdown server + posix_kill($pid, SIGTERM); + parent::deletePid(); + + $output->writeln(sprintf('Stopped %s WebSocket server', $name)); + + } +} + +?> \ No newline at end of file diff --git a/Command/WebsocketCommand.php b/Command/WebsocketCommand.php new file mode 100644 index 0000000..8b1c39c --- /dev/null +++ b/Command/WebsocketCommand.php @@ -0,0 +1,139 @@ +name = $name; + } + + /** + * Get full path for pid file + * + * @return string + */ + private function getPidFileName() + { + return sys_get_temp_dir().'/websocket-'.$this->name.'.pid'; + } + + /** + * Check existence of pid file + * + * @return boolean + */ + protected function checkPid() + { + return file_exists($this->getPidFileName()); + } + + /** + * Delete pid file + * + * @throws WebSocketException + * @return boolean + */ + protected function deletePid() + { + if (!$this->checkPid()) { + throw new WebSocketException("Pid file doesn't exist", 1); + } + if (!unlink($this->getPidFileName())) { + throw new WebSocketException("Can't delete pid file.\n".error_get_last(), 4); + } + return true; + } + + /** + * Get websocket daemon process number + * + * @throws WebSocketException + * @return integer + */ + protected function getPid() + { + if (!$handle = fopen($this->getPidFileName(), "r")){ + throw new WebSocketException("Can't open pid file.\n".error_get_last(), 2); + } + if (!$pid = fread($handle, filesize($this->getPidFileName()))) { + throw new WebSocketException("Can't read pid file.\n".error_get_last(), 3); + } + return $pid; + } + + /** + * Create pid file with process number inside + * + * @throws WebSocketException + * @return boolean + */ + protected function createPid() + { + if (!$handle = fopen($this->getPidFileName(), "w") ){ + throw new WebSocketException("Can't open pid file.\n".error_get_last(), 2); + } + if (!$pid = getmypid()) { + throw new WebSocketException("Can't get current php process id.\n".error_get_last(), 6); + } + if (!fwrite($handle, $pid)) { + throw new WebSocketException("Can't write into pid file.\n".error_get_last(), 5); + } + return true; + } + + /** + * Handle process's signal (sigterm) and throw exception for future catching + * + * @throws WebSocketSignalException + */ + static function handleSignal() + { + pcntl_signal(SIGTERM, function($signo) { + throw new WebSocketSignalException('Received SIGTERM signal', 12); + }); + } + + /** + * Check system signals and return false in time of sigterm recieving + * + * @throws WebSocketException + * @return boolean + */ + static function listenPid() + { + sleep(1); + try { + if (!pcntl_signal_dispatch()) { + throw new WebSocketException("Can't dispatch signals.\n".error_get_last(), 7); + } + } catch (WebSocketSignalException $e) { + return false; + } + return true; + } + +} +?> diff --git a/Exception/WebSocketException.php b/Exception/WebSocketException.php new file mode 100644 index 0000000..10dd844 --- /dev/null +++ b/Exception/WebSocketException.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/Exception/WebSocketSignalException.php b/Exception/WebSocketSignalException.php new file mode 100644 index 0000000..9076579 --- /dev/null +++ b/Exception/WebSocketSignalException.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/Server/DaemonServer.php b/Server/DaemonServer.php new file mode 100644 index 0000000..b176e7f --- /dev/null +++ b/Server/DaemonServer.php @@ -0,0 +1,36 @@ +connectionManager->listen(); + + if (!is_callable($finish) ) { + $finish = function() { return true; }; + } + + while ( $finish() ) { + $this->connectionManager->selectAndProcess(); + + foreach($this->applications as $application) { + if(method_exists($application, 'onUpdate')) { + $application->onUpdate(); + } + } + } + } + + + +} +?> \ No newline at end of file