📜 ⬆️ ⬇️

Linux demon on PHP5

Good day, today I will describe a rather amusing puzzle, from the domain of a little directly connected with web programming, or rather the creation of a PHP daemon. It is clear that the first question will be: "Why is it necessary?" Well, we will deal sequentially.


It would seem that a rare perversion to write programs of this kind in languages ​​like PHP, but that if there is a need to continuously monitor or process any continuous or non-regular process, and a small script is needed. As a rule, you don’t have a competent specialist who can’t shoot through your leg with C ++ or don’t cut off your leg with C, or just a good application programmer. In these cases, each is spinning as it may, and there appear a wide variety of chimeras and hybrids, such as scripts running with the set_time_limit (0) parameter, scripts starting with cron every second (yes, yes, I saw this) and other, not less crutchy things.

Actually about the formulation of the problem. In the process of working on a project, the need to integrate with an external program complex has emerged. The only way to communicate with the program is to communicate by means of its own protocol through the network port, wait for the event to occur, parse the answer, process, save to the database. It would seem difficult to write a script with an infinite loop, inside which all the necessary magic would happen and voila! At the very beginning, I argued about the same, but it soon became clear that this approach has a significant drawback, one of which is incorrect data appearing at the time of the death of the script. The server reboots and data remains in the database that could not be processed or deleted. Unpleasant, very unpleasant.
')
Well, let's understand, we have a classic LAMP on CentOS, third-party software and a strong desire not to fasten any other tools or “God forbid to program in C”. I thought that it would not be bad at all if the original script could be taught to recognize the signals of the operating system so that it completes its work correctly. For those who don’t know, in general terms, Linux manages processes with signals that tell the process how it should behave. Upon receipt of such a signal, the process must change its behavior or do nothing if it is not required for its operation. For me personally, the SIGTERM signal is most interesting. This signal indicates that the process should complete its work. A list of all existing signals can be found here:
UNIX signals

There is also another feature, each process in Linux is somehow connected with the terminal from where it was launched and inherits input / output streams from it, so as soon as you close the terminal in which you started the script, it will immediately complete its execution. In order to avoid such a situation, you need to create a child process, make it basic, finish off the parent and untie the remaining process from the input / output of the terminal in which it was running. I agree, it sounds complicated, confusing and not clear, but in practice everything is much simpler than it looks.

What do we need to work? In principle, not so much, actually PHP itself, in my case this PHP5.6 and several extensions:


Now that we have everything we need, let's start writing the code.

As mentioned above, to start our daemon, in accordance with the rules of work of similar programs in Linux, it must be untied from the terminal where it was launched. To do this, use the pcntl_fork () function, it creates a child copy of the current process and returns its numeric id if successful. And of course to finish off the parent process.

<?php //    $child_pid = pcntl_fork(); if( $child_pid ) { //    ,   ... exit(0); }         ,      . //    ... posix_setsid(); 

Thus, the operating system will know that we are capable of determining our behavior and will place the pid of our process in a queue for receiving system signals.

Now the most interesting thing awaits us - it is necessary to determine exactly how we will work and interact with the operating system. I decided to bring this functionality to a separate class, just in case this code is still needed. Let's begin, for a start it is necessary to decide what the class should be able to do.

Receive and process operating system signals;
Be able to understand whether a demon is running or not;
Run the task necessary for demonization;
Know when to stop;

To implement these tasks, it is worth analyzing the functions that will be useful to us. The pcntl_signal () function is needed in order to assign a handler function to a signal. Takes as arguments: the signal for which the handler is assigned and the function or method of the class responsible for processing the signal. The getmypid () function, which returns the pid of the current process. Finally, the posix_kill () function, which sends a signal to the specified process, takes two arguments: the pid of the process to which the signal should be sent and the signal itself, which should be sent.

In order to control the state of our own process, we need a flag that determines whether it is time to complete the process or not. There is also some subtlety, in order to save CPU resources, it is necessary to make pauses, during which the application will just wait for the next iteration of the cycle, thereby not loading the system with constant requests. You must define these parameters as class fields.

 <?php class Daemon { protected $stop = false; protected $sleep = 1;        ,    : //     public function signalHandler($signo) { switch($signo) { case SIGTERM: //       ... $this->stop = true; break; // default: //             ... } } 

As you can see, the method accepts a signal as an argument, which is sent to it, and depending on what signal is sent to the daemon, it performs certain actions.

Now we need to know for sure whether our demon is running or not. How would we do this, because the demon can be run from different terminals or several times in a row. If several instances of the same script will try to simultaneously access the same resources, I think it will be extremely unpleasant. To avoid this situation, we can use the old way as the world, during the execution of the program, create a file in a certain place with the program process pid recorded there and delete it every time our application closes. Thus, by checking for the existence of this file, we can know whether there are running copies of our application. To solve this problem, we define the method of our class.

 //       ,        ... public function isDaemonActive($pid_file) { if( is_file($pid_file) ) { $pid = file_get_contents($pid_file); //    ... if(posix_kill($pid,0)) { //   ... return true; } else { // pid- ,   ... if(!unlink($pid_file)) { //    pid-. ... exit(-1); } } } return false; } 

Here we check all possible variants of events, whether the file exists, and if yes, what process was created, check whether such a process exists, if the process that created the file does not exist, since the process ended unexpectedly, try to delete the file.

It's time to think, and how are we going to perform the actual operations for which everything was intended? There are many implementation options. In my case, it was necessary to wait for the results from the third-party service, without this data the process itself was useless and did not perform any actions on the existing data or resources, so I implemented all the processing into a function that received or did not receive data from the third-party service. If there was no data, the function should be called until they appeared. Thus, the class method I wrote, which implements the payload, depended on two parameters: the internal state of the daemon and the results of the work of the data processing function from the third-party service.

 //             ... public function run($func) { //  ,   ... while(!$this->stop){ do{ //     ... //    ... $resp = $func(); //   ,    ... if(!empty($resp)){ break; } //   ,    ... }while(true); sleep($this->sleep); } } 

Thus, I have two conditionally infinite loops, an inner one, which waits until the function is executed and an outer one, which waits until the state of the daemon changes. Nobody says that my implementation is the most correct, the implementation of the method can be overridden as convenient, not forgetting to watch whether it is time to complete the process or not.

Now comes the climax, you need to tie it all together and run, I think it's time to write a constructor for our class.

 //          pid           ... //      ,   ... public function __construct($file = '/tmp/daemon.pid',$sleep=1) { //      ... if ($this->isDaemonActive($file)) { echo "Daemon is already exsist!\n"; exit(0); } $this->sleep = $sleep; //  ,       ... pcntl_signal(SIGTERM,[$this,'signalHandler']); //  pid      getmypid()     pid ... file_put_contents($file, getmypid()); } 

We check with the help of the file whether the process is running or not, if it is running, display the corresponding warning, set a delay, assign signal handlers (in this case only one), create a file and write our pid there, so that we know other copies of the process that we are already working. We are done with the class.

Now back to writing the demon script itself. We settled on the fact that we finished all the preparations for launching the demon.

 //      ... include(__DIR__.'/Daemon.php'); include(__DIR__.'/ExampleClass.php'); //    ... $example = new ExampleClass(); //        ,    ... //    -               ... $func = function() use ($example){ //     ... $example->test(); return true; }; //   ,       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'); //     ... $daemon->run($func); 

We connect all the libraries we need, including the file with our Daemon.php class, describe the function that will perform the payload, create an instance of the Daemon class with the parameters we need, untie the standard I / O from the current terminal and redirect them in / dev / null (if we would have done it earlier, we would have risked not seeing error messages during the script execution), passing the daemon class to the run method, our function, which will be executed by the daemon.

That's all. Our daemon works and communicates perfectly with the OS. All good and good luck.

PS The source codes for the daemon are available here: https://github.com/Liar233/php-daemon/ .

Source: https://habr.com/ru/post/276709/


All Articles