This is more of a general question before I start building the solution so that I don't waste my time.

I have written from first principles a multi-user game socket server, or at least the beginnings of one. At the moment users can connect and chat, but that's about it, however the structure is in place for games to be played in the rooms it serves.

Each room is an object, as is each player, with one attribute of the player being the Socket resource for communication.

I am going to need to be able to pause processing, but only in one room or for one player at a time. An example would be when there are 3 or more players in the room, a timer will kick off waiting, say, 5 seconds for more players to join, and after 5 seconds if no more players have joined, a game will start.

So my question is, if I wrote a Timer class which waits the 5 seconds, utilising the sleep() function, and associated each room with a Timer object, will the call to this halt the entire PHP script from executing, or will it pause only that room?

If the former, how would you suggest I get round this problem?

Thanks for any help 😃

    Yes, if you [man]sleep[/man] then your whole script should go to sleep unless you have forked off some other processes from within your PHP script. When I say 'forked off' you might get confused. The basic idea is that you are touching on the whole concept of threads here. PHP supports POSIX functionality. I'm not certain, but I believe that is one way to get threads rolling in your PHP applications. Another is pcntl.

    However, it is NOT recommended to try multithreaded stuff in PHP -- at least when when working with Apache. I understand from a guy here named Shrike that PHP is itself threadsafe (meaning what exactly I don't know) but that some of its libraries may not be.

    I'm not sure what to tell you as I am just getting into this stuff myself.

      sneakyimp wrote:

      I'm not sure what to tell you as I am just getting into this stuff myself.

      It shows: you're confusing threading with interprocess communication 🙂

        It is with the utmost respect weedpacket that I say you are being rather condescending (dare I say pedantic?). I do feel I've learned something from what you've posted but am left wanting in a most dire way. Please do tell us a little more.

          sneakyimp;10908883 wrote:

          Please do tell us a little more.

          fo shizzle my weedilze

            Definitely pedantic.

            The thing about threads is that they all share as much process state as possible; they're all looking at and writing to the same variables in the same locations in memory. Which means that without careful planning it's all too easy to have one thread overwrite something that another thread was trying to use. A multithreaded program is one program with multiple threads.

            If you fork off another process, that's another process; the operating system sees it as a completely separate entity; the two processes have their own memory space, their own state, and one can crash without affecting the other. It actually requires an effort to get them to communicate; unlike threads, where the problem is to stop them from communicating.

            Google make something of a deal about Chrome being multi-process rather than multithreaded.

              Ok, well your help is appreciated of course, but I'm still unclear as to potential solutions.

              Should I go down the threading/forked process routes, or is that overkill for what I'm trying to achieve?

              Is there any other way to achieve a delay in processing in the room object itself rather than pausing the execution of the whole script?

                The only way I can think of to even roughly approximate this (without multiple threads of execution) is to store a value for the start of your five-second timer and have a check in each iteration of your main loop which checks to see if five seconds have passed. Unfortunately, if your socket listening/reading is set to block while waiting for input, then the 5-second timer may never be triggered unless you have a bunch of people communicating with your server. Perhaps even worse, if your socket listening/reading call does NOT block, then you have a loop that is really chewing up some CPU time spinning around and doing nothing.

                This is the sort of thing that multiple processes and/or multiple threads is designed for and if you can get it working, then it should be quite an effective solution. I'm not exactly sure how to get inter-process communication working in PHP. It would probably be something where you create a process to handle each 5-second timer. Basically, you create a new process that just sleeps for five seconds and then taps your main server process on the shoulder saying "I'm finished".

                There are probably other ways to use additional processes in this context. Try reading the pcntl/POSIX links I sent. I'm not sure how I got threads and processes confused in my post, but it may become clear in reading them.

                  Don't know if anyone is still watching this, however I'll post here at least for the benefit of anyone who comes across this thread with a similar problem.

                  First things first, many thanks to all who replied, but a particular big thank you to SneakyImp for your comments thus far. Hope you keep contributing because far and away your comments have been the most valuable.

                  I have done lots of research into PCNTL and POSIX technology within PHP thanks to to your pointers.

                  The sad fact is that PHP does NOT support threading. There are ways to mimic it, however I haven't looked into those very deeply. The chances are they'll be unsuitable for real-time timer applications anyway, either because they're too resource greedy or that they don't adhere to the same rules as, for example, Java or C# threads.

                  So, the basic methodology I'm going to try implementing first is as follows:

                  Within my Room class:

                  class Room {
                  
                  /* other attributes */
                  protected $cpid; // child process ID
                  
                  
                  public function __construct($iName, $iPlayers, $minPlayers) {
                      /* other instantiations */
                      $this->_cpid = 0;
                  }
                  
                  public function addPlayer (Player $p) {
                  
                      /*stuff to add player to appropriate player lists goes here */
                  
                      if ($this->_cpid > 0) {
                  
                          // there is already a child process
                          posix_kill($this->_cpid, SIGTERM);
                          $this->_cpid = 0;
                  
                      }
                  
                      $cpid = pcntl_fork();
                  
                      if ($cpid != 0) {
                  
                          $this->_cpid = $cpid;
                          $gTimer = new GameStartTimer($this, 5);
                          $gTimer->start();
                  
                      }
                  
                  
                  }
                  
                  public function startGame () {
                  
                  echo "Game starting in ".$this->_name();
                  $this->_cpid = 0;
                  
                  return;
                  // will start the game
                  
                  }
                  
                     /* other room stuff */
                  
                  }
                  
                  

                  So there'll be a child process ID within the Room class - if it's populated, then a child process already exists - i.e a timer is already going. Therefore kill it and start a new one.

                  And the game timer itself:

                  class GameStartTimer {
                  
                  //protected $start_time;
                  protected $elapsed_time;
                  protected $trigger_time;
                  protected $tparent;
                  
                  public function __construct($triggerTime, $timerParent) {
                      //$this->_start_time = microtime();
                      $this->_elapsed_time = 0;
                      $this->_trigger_time = $triggerTime;
                      $this->_tparent = $timerParent;
                  }
                  
                  public function start() {
                  
                      while ($this->_elapsed_time <= $this->_trigger_time) {
                  
                          sleep(1);
                          $this->_elapsed_time = $this->_elapsed_time + 1;
                  
                      }
                  
                      pcntl_signal(SIGUSR1, array($tparent, "startGame"));
                  
                      posix_kill(posix_getpid(), SIGUSR1);
                  }
                  
                  public function getParent() {
                  
                      return $tparent;
                  
                  }
                  }

                  The idea behind this is, send a signal back to the parent to let it know that the timer has finished and the game can begin.

                  It may need tweaked a little. I'm basically crossing my fingers that the pcntl_signal function will work. Lots of people have been having problems with it.

                  If you can suggest a better way/improvements to this, or you don't think this will work, please let me know ASAP.

                  Thanks again for the help so far!

                    I'm not sure I see any reason why you shouldn't just sleep for the whole 5 seconds. Keep in mind that your child process must wait to be re-scheduled after the sleeping is over. To sleep and wake up five times might introduce more random variations in the actual length the timer is asleep.

                    Other than that it seems to make sense. Each time you call addPlayer, you cancel any existing timer process and then start a new one. If in the next five seconds nothing happens, you should get a call from your timer process returning.

                    Does it work?

                      Does it work?

                      It would appear it doesn't... D'OH!

                      I've had to tweak the listed code slightly as there are a few errors - the most obvious being the parameters in the call to GameStartTimer are the wrong way round.

                      The new process is created correctly, and the timer runs, but the call to the object function never happens, and therefore the game never starts. This is kind of a key issue!

                      However as I said, I've heard of people having this same problem. So tomorrow night I might try to write a generic sig_handler function global to the script which will match the child process id (obtained via a call to pcntl_wait within sig_handler - only just found out that this can be done) to the Room object, and call the associated startGame function.

                      I think this is much more likely to work, but it will just be a bit of a faff matching the child process ID to the room. I'll just have a getCpid function which returns the child process id for the room, and then loop round each room checking for a matching id.

                        a month later

                        OK I've finally got around to reading a little bit about pcntl_fork. As far as I can tell, your Room class is doing a couple of things wrong.

                        You are trying to call the timer in your parent process and you are not doing anything with your child process once you've forked it. Also, you are not properly setting up a process signal handler in order to be notified when the child process terminates. [man]pcntl_fork[/man] is kind of a wacko function and causes your code to do something which may just boggle your mind because it's totally nonlinear. The basic idea is that once you call pcntl_fork, you have two different processes running the exact same code -- but only if pcntl_fork works and doesn't return -1.

                        Consider your code:

                                $cpid = pcntl_fork();
                        
                            if ($cpid != 0) {
                        
                                $this->_cpid = $cpid;
                                $gTimer = new GameStartTimer($this, 5);
                                $gTimer->start();
                        
                            } 
                        

                        All the examples I've seen check for 3 conditions: -1, 0, and >0. There's a good description of everything here. Be sure that you do too...you may get a -1 in which case it's not working. Think about something like this:

                                $cpid = pcntl_fork();
                        
                            if ($cpid == -1) {
                                die('Unable to fork child process');
                            } elseif ($cpid == 0) {
                                // if this code gets executed, it is getting executed by the child process
                                // put your timer code here.
                                $gTimer = new GameStartTimer(5, $this);
                                $gTimer->start();
                            } elseif ($cpid != 0) {
                                // this code gets executed by the parent process
                                // keep a reference to the child's process id
                                $this->_cpid = $cpid;
                                // set up signal handler?  not sure what else to do here...probably waitpid.
                            } 
                        

                        Also, you have your args reversed when you create your GameStartTimer. I switched them back.

                        If I were you, I would define a signalHandler function in my Room class. something that accepts one argument $signo:

                            public function sigHandler($signo) {
                                if($signo == SIGTERM) {
                                    // you should probably write a log file instead of echoing
                                    echo "sigterm received!";
                                } elseif($signo == SIGHUP) {
                                    echo "sighup received!";
                                } elseif($signo == SIGUSR1) {
                                    // is this not the signal you were trying to send?
                                    $this->startGame();
                                } else {
                                }
                            }
                        

                        Then you should change your pcntl_signal function to call the signalHandler method:

                                pcntl_signal(SIGUSR1, array($tparent, "signalHandler"));

                        I have no idea if this stuff will help. Please let me know if it does and maybe post your code if it works?

                          Very sorry. I guess I got so excited about having successfully got the code to work that I forgot about posting back on here.

                          For the sake of completion, and, as I say the best thing that could happen is that it helps someone else, I'll post my code here:

                          As you rightly say, pcntl_fork() creates a child process, therefore:

                          AddPlayer function, within room class:

                              public function addPlayer (Player $p) {
                          
                              $this->_playerlist->addPlayer ($p);
                          
                              if (($this->_name != 'Lobby') && ($this->$_gameStatus < 5)) {
                          
                                  if ($this->_cpid > 0) {
                          
                                      // there is already a child process
                                      posix_kill($this->_cpid, SIGTERM);
                                      $this->_cpid = 0;
                          
                                  }
                          
                                  $cpid = pcntl_fork();
                          
                                  echo 'CPID: '.$cpid;
                          
                                  if ($cpid > 0) { //  parent
                          
                                      $this->_cpid = $cpid;
                          
                          
                                  }
                                  else if ($cpid == 0) { //  child
                          
                                      $gTimer = new Timer($this->_gameStartTime, $this);
                                      $this->sendAllPlayers ('\GAME START WAIT '.$this->_gameStartTime);
                                      $gTimer->start();
                                      exit( 0 );
                          
                                  }
                          
                                  else {
                                      exit( 0 );
                                  }
                          
                              }
                          
                          
                          }

                          Hopefully this is fairly self explanatory, particularly after your explanations in your last post. We spawn off a new child which will deal with the timer. If there's already a child process carrying out the timer, it is killed before it finishes, thereby resetting the timer.

                          Timer Class:

                          class Timer {
                          
                          //protected $start_time;
                          protected $elapsed_time;
                          protected $trigger_time;
                          protected $tparent;
                          
                          public function __construct($triggerTime, $timerParent) {
                              //$this->_start_time = microtime();
                              $this->_elapsed_time = 0;
                              $this->_trigger_time = $triggerTime;
                              $this->_tparent = $timerParent;
                          }
                          
                          public function start() {
                          
                              echo 'Timer Started';
                          
                              while ($this->_elapsed_time <= $this->_trigger_time) {
                          
                                  sleep(1);
                                  $this->_elapsed_time = $this->_elapsed_time + 1;
                                  echo 'elapsed:' . $this->_elapsed_time . '/n';
                          
                              }
                          
                              echo 'calling sig_handler of ppid '.posix_getppid();
                          
                              //pcntl_signal(SIGUSR1, array(&$this->_tparent, "testMessage"));
                          
                          
                              posix_kill(posix_getppid(), SIGUSR1);
                          
                              //exit( 0 );
                              //posix_kill(posix_getpid(), SIGTERM);
                          }
                          
                          public function getParent() {
                          
                              return $tparent;
                          
                          }
                          }

                          You are 100% right about the signal handler. As follows:

                          function sig_handler($signo)
                          {
                          
                          global $allRooms;
                          
                          echo "in sig handler";
                          
                           switch ($signo) {
                               case SIGTERM:
                                   exit;
                                   break;
                               case SIGUSR1:
                          
                                   $child_process_id = pcntl_wait($status);
                          
                                   for($i=1; $i < $allRooms->getNumRooms(); ++$i) 
                                   {
                                       $curRoom = $allRooms->getRoom($i);
                                       if (($allRooms->getRoom($i)->getCpid() == $child_process_id) &&
                                           ($allRooms->getRoom($i)->getName() != 'Lobby') &&
                                           ($allRooms->getRoom($i)->getCpid() != 0))
                                       {
                          
                                           $curRoom->timerTrigger();
                          
                                       }
                          
                                   }
                          
                                   break;
                               default:
                                   break;
                                   // handle all other signals
                           }
                          
                          }

                          Though, I have to figure out which room the timer is trying to tell me about, hence the for loop which checks the cpid values of each room, and tries to start timerTrigger(), which is the new startGame() effectively, but I've added more to that as well.

                          I couldn't get the object passing part of pcntl_signal to work, so I gave up and went with this more messy, but working solution.

                          Let me know if you can see any possible improvements or enhancements, or have any further questions.

                          Thanks again for your help.

                          Dave

                            You might want to use the declare statement before declaring your signal handler...not sure exactly what this does, but I believe that it tells PHP that the code that follows can be interrupted in order to run a signal handler:

                            declare(ticks=1);

                            Something I'm curious about personally is what happens to vars in your parent process when you increment or alter them in the child process. Unless I'm mistaken, the child inherits parent process' variables and if the child changes them a COPY is made for the child process such that the change has no impact on the parent process. Could you check this for me? I understand if you are too busy but I'm dying to know if a child can alter the value of variables in its parent process. If so, there would be your answer.

                            I think you should revisit your attempt at [man]pcntl_signal[/man] and try passing an object variable again. I am not at all sure, but I believe that when you call exit(0) in the parent process that is where your problem might be because it will END the parent process entirely meaning that the parent process is not alive to receive a signal. If you did perhaps [man]pcntl_waitpid[/man] at that point then you might have a chance to execute code in the Room class itself in which case the room data would be available as $this.

                            Keep in mind that you would need to define a method signalHandler in your Room class if you do this in your Timer class:

                            pcntl_signal(SIGUSR1, array($this->_tparent, "signalHandler")); 

                            I don't see in your current code where you are assigning the signal_handler you have. I just don't understand how your parent process would ever run the signal handler if it calls exit immediately after forking. You might want to add some echo statements or something in your code where the parent process declares its PID and so do the child processes. I think that would be helpful in determining which process is actually running the signal handler.

                            Another idea might be in your parent process to keep an array of Rooms waiting on timeouts indexed by the PID of the child timers. When the signal handler gets a signal from some process and you retrieve the child's PID with this:

                                         $child_process_id = pcntl_wait($status); 

                            Then you might use $child_process_id as an array key to find the correct Room in the array.

                            Sorry if my advice sounds disjointed or stupid, It's just been so long since I tried programming with this concurrent execution stuff and I never understood it that well in the first place.

                              4 days later
                              dmorison;10908794 wrote:

                              This is more of a general question before I start building the solution so that I don't waste my time.

                              I have written from first principles a multi-user game socket server, or at least the beginnings of one. At the moment users can connect and chat, but that's about it, however the structure is in place for games to be played in the rooms it serves.

                              Each room is an object, as is each player, with one attribute of the player being the Socket resource for communication.

                              I am going to need to be able to pause processing, but only in one room or for one player at a time. An example would be when there are 3 or more players in the room, a timer will kick off waiting, say, 5 seconds for more players to join, and after 5 seconds if no more players have joined, a game will start.

                              So my question is, if I wrote a Timer class which waits the 5 seconds, utilising the sleep() function, and associated each room with a Timer object, will the call to this halt the entire PHP script from executing, or will it pause only that room?

                              If the former, how would you suggest I get round this problem?

                              Thanks for any help 😃

                              Personally I would opt for some shared memory, and simply make note of
                              the time as folks sign in and give em a timed refresh or something.
                              sleep() has always been a lousy idea in my book.

                              Of course I've only been coding since before the advent of the PC
                              so what would I know 🙂

                                a month later

                                Yet again, I apologise for taking a long time to respond. However I wanted to post an update as I think I have now got this sorted out.

                                This may not be the most elegant solution on the planet, but it does seem to work, and I would hope that it may help out someone who's after the same thing.

                                Ok, so just to summarise first of all, the solution I am about to propose is to allow Sleep() calls in a PHP socket chat room/mp gaming server which will pause only one room at a time and allow all other rooms, and the main thread, to continue as normal. The code displayed here has been greatly stripped down to show only the necessary for this. If you'd like any assistance with any other aspect of the server, please contact me by PM.

                                This is just one of the many possible solutions. With the benefit of hindsight, I can see that possibly having each room created as it's own sub process would probably have been the smartest thing to do. However I have not gone down that route. In general, room processing is done by the parent process. Ok, so first off, this is vital as SneakyImp correctly pointed out:

                                declare(ticks = 1);

                                Now, the Room class:

                                class Room {
                                
                                
                                protected $cpid; // child process ID
                                
                                protected $playerlist ;
                                
                                
                                public function __construct() {
                                    $this->_playerlist = new PlayerList ();
                                    $this->_cpid = 0;
                                
                                }   
                                
                                
                                public function removePlayerNick ($nick) {
                                
                                    $this->_playerlist->removePlayerNick($nick);
                                
                                
                                        if ($this->_cpid != 0) {
                                
                                            posix_kill($this->_cpid, SIGTERM);
                                            pcntl_waitpid ($this->_cpid, $status);
                                            $this->_cpid = 0;
                                
                                        }
                                
                                 }
                                
                                public function addPlayer (Player $p) {
                                
                                    $this->_playerlist->addPlayer ($p);
                                
                                        if ($this->_cpid > 0) {
                                
                                            // there is already a child process, so kill it, return the process ID and reset the room's CPID
                                            posix_kill($this->_cpid, SIGTERM);
                                            pcntl_waitpid ($this->_cpid, $status);
                                
                                            // this is absolutely vital (as I've discovered), otherwise calling waitpid later is unpredictable
                                            $this->_cpid = 0;
                                
                                        }
                                
                                        $cpid = pcntl_fork();  // Fork off a new child process
                                
                                        echo 'CPID: '.$cpid;
                                
                                        if ($cpid > 0) { //  parent, so record new CPID and do whatever parent processing is required
                                
                                            $this->_cpid = $cpid;
                                
                                
                                        }
                                        else if ($cpid == 0) { //  child - set up the timer to delay the parent room.  
                                        //Note that the parent is still receptive to incoming socket data after this child has spawned
                                
                                            $gTimer = new Timer(20); // pause for 20 seconds
                                
                                            $gTimer->start();
                                            exit( 0 );
                                
                                        }
                                
                                        else {
                                        // not good - error handling should go here
                                            exit( 0 );
                                        }
                                
                                
                                }
                                
                                public function timerTrigger () {
                                
                                    $this->_cpid = 0;
                                
                                     // This is where you should define all the things to do after pausing.
                                     // In the actual server, I've defined a game status variable to hold the status of the room
                                     // and increment it as it progresses through the stages of the game.
                                
                                     // This would say things like 'If status is 1, then we have finished waiting on more players and game can begin'
                                
                                    }
                                
                                }

                                I've tried to add comments in the code, and I hope it's fairly self explanatory. I've left in the AddPlayer and RemovePlayer code, or some of it, in the hope that it demonstrates usage of the Timer/Sleep system. It will kick off a timer when a player enters the room. In the actual server, the room's current status is being checked to ensure that this it the state the room is in. If another player enters the room while it's waiting for players, the timer is reset (the process is killed), and a new timer is started which, if it finishes successfully, will start the game.

                                The WaitPid in both of these procs is the solution to a massive and long running problem I was having whereby unpredictable results were occurring, because the system was queuing up all the dead process ids which were hanging around during my ACTUAL call to waitpid, which will be shown later.

                                Ok, now the Timer Class:

                                class Timer {
                                
                                protected $elapsed_time;
                                protected $trigger_time;
                                
                                public function __construct($triggerTime) {
                                    $this->_elapsed_time = 0;
                                    $this->_trigger_time = $triggerTime;
                                }
                                
                                public function start() {
                                
                                    while ($this->_elapsed_time <= $this->_trigger_time) {
                                
                                        sleep(1);
                                        $this->_elapsed_time = $this->_elapsed_time + 1;
                                
                                    }
                                
                                    posix_kill(posix_getppid(), SIGUSR1);
                                
                                }
                                
                                
                                }

                                Basically what this is doing, is running out the child process for the specified amount of time (yeah, I know I haven't taken the already given advice on this, but it seems to work ok as it is :-). Once the timer has run out, a SIGUSR1 is passed to the parent id and the child dies. Then, you need this function to pick up the signal back at the parent:

                                function sig_handler($signo)
                                {
                                
                                global $allRooms;
                                
                                 switch ($signo) {
                                     case SIGTERM:
                                         exit;
                                         break;
                                
                                     case SIGUSR1:
                                
                                         $child_process_id = -1;
                                
                                         $child_process_id = pcntl_wait($status);
                                
                                         for($i=1; $i < $allRooms->getNumRooms(); ++$i) 
                                         {
                                             $curRoom = $allRooms->getRoom($i);
                                             if (($allRooms->getRoom($i)->getCpid() == $child_process_id) &&
                                                 ($allRooms->getRoom($i)->getCpid() != 0))
                                             {
                                
                                                 $curRoom->timerTrigger();
                                
                                             }
                                
                                         }
                                
                                         break;
                                     default:
                                         break;
                                
                                 }
                                
                                }

                                Those of you still following this long winded story will note that this completes the circle - the child process calls this procedure within the parent, though of course both the Timer class and this function are within the same physical block of code, but pcntl_fork splits the process in two, so you have two identical programs running at the same time. So the method described here gives you one way of dealing with these forked processes.

                                Once this is called within the parent process, it will then match the child process ID to that of the appropriate room, and invoke the timerTrigger function of that room. The room will then check its own status and, assuming this is adequately maintained, carry out the correct processing based on that status. The cycle then starts again, and any time a pause is required, a new Timer is started, and the CPID is recorded to be caught later when the sig_handler does a sweep of all the rooms again.

                                Phew!

                                Again, thanks for the help of everyone on the forum, but in particular, a massive thank you to SneakyImp who has been the closest thing to a mentor on this project that I have had!

                                The project is now up and running, so to see the server in action, feel free to visit www.captionthat.com . But don't bring too high expectations :-)

                                It's pretty dead around there just now, but I'm improving it all the time, so hopefully one day I will have rooms full of users! In particular, feel free to come and populate/join my forum. It feels very lonely :-)

                                  Write a Reply...