Multiclient Server

The server-side changes are a bit deeper. Instead of the previous single- user solution, we need to create and track an entire list of client sockets. The way this works is not terribly different than what we did before, except that we do it repeatedly.

We need to establish a publicly known port to which we bind a listening socket. We listen for connection requests . Clients that request connections are each given an individual dedicated connection socket, and these are stacked in the server (Figure 20.3).

Figure 20.3. Six Steps to Connect Three Clients


 <?php header("Content-type: text/xml"); 

We are switching from an HTML monitor interface to an XML interface. This prepares us for a Flash control system, not a simple browser-based one. However as long as we use an XML-ready browser (like IE5.0 and beyond), it will also provide a fine browser interface.

The next two functions are unchanged from the single-serving version in the previous chapter. They are simple utilities.

 function check( $value, $function ) {    . . . . . etc } function superflush() {     . . . . . etc } function closedown() { global $port, $listener, $guests; echo "<release> Freeing Port $port and this list:<br/>";   echo "Listening Socket ".$listener."<br/>";    for( $i=0; $i<count($guests); $i++) {        echo "Client Socket ".$guests[$i]."<br/>";        close( $guests[$i] );        }    echo "</release>";    superflush(); } 

The cleanup function has been extended. It originally shut down only two sockets, the listener socket and the message socket. Now it has a whole list of message sockets to process, as well as the listener socket. The list is an array called guests.

Notice that the output formatting has changed somewhat. The progress is now reported in XML, not HTML.

 set_time_limit(120); register_shutdown_function ("closedown" ); $address = gethostbyname ($SERVER_NAME); $port =    12536; $maxport = 12546; $guests = array(); $welcome= "<WELCOME>Welcome to our little party</WELCOME>";    check( $listener =   socket(AF_INET,SOCK_STREAM,0),  "socket" );    while(  ($errno = bind( $listener, $address, $port)) <0 )       if( ++$port > $maxport)             die("<error>Tried up to $port".strerror( $errno )."</error>" );       else             echo("<try>".strerror($errno)."] Try $port<br/></try>\ n" );   print( "<port>$port</port>" );   superflush(); 

In this version, we are contending with a common development problem. As we debug small errors in our code, we need to run our software again and again. Often, when we try to reopen our assigned port soon after a small failure, we find it unavailable. The severity of this problem naturally depends on how serious the bugs are.

We are careful to put time limits on our scripts (see the first line). If we are patient, our system seems eventually to recover the locked-out ports. But the wait is often annoying.

The best solution is to make no errors. That's tough.

Our approach is to have the system administrator allocate a small range of port numbers for developmental use. The preceding code runs through this range seeking the first available port. It displays this successful port number so that we can then run our clients addressed to this port. The publication of this port number is in XML. We can read this number in an XML-ready browser and type it into the client. Of course a smart client would be able to read the XML itself, through an HTTP connection, and set its new port target automatically.

 check( $ret = listen( $listener, 5), "listen" );   set_nonblock ( $listener );   do {       if( ($newguest= accept_connect( $listener )) >0 ){          write( $newguest, $welcome, strlen($welcome));          set_nonblock ($newguest );          array_push( $guests, $newguest );          } 

The first step in the loop is to check for new users. Fortunately, nonblocking mode works well on listening sockets even in PHP 4.0.4, so if there are no pending connections to accept, we fall through the conditional immediately.

If a pending connection is found, the new guest is sent a welcome message. We set the new guest socket to be nonblocking as well and add it to our list of guests.

 $bufs=array();      $buf="";      for ($i=0; $i< count( $guests ); $i++)         if(   read(  $guests[$i], $buf, 48) )             array_push( $bufs, "<msg>"                 .strtoupper( strip_tags( $buf)  )."</msg>" ); 

The second step in our loop is to collect all the incoming text. The input line, $buf, and the array of such lines, $bufs, are both cleared. Each guest is checked to see if there is incoming data. If there is no data (and if your version of PHP ”unlike 4.0.4 ”properly handles nonblocking reads), execution falls through the conditional. (Falling-through behavior is important, especially in this multiplexed server. We need to check all the guest connections for input. With only blocking reads, every client must contribute input to every cycle!)

Otherwise, the old tags are stripped off and the string is converted to uppercase (just for demonstration purposes). Fresh tags are applied, along with a proper null terminator. If more than one guest has something to say during this particular second, the lines are stacked up. In the case of a small number of guests with normal human typing abilities , this would be a rare occurrence. Ignoring the pending messages would not be critical; the overlooked messages would be picked up a second later. But our XMLSocket system works with data as easily as text, and the messages might be (for example) real-time game updates that are copiously generated and latency-sensitive. We would not want to backlog them.

 for ($i=0; $i< count( $guests ); $i++)           for ($b=0;  $b< count($bufs); $b++)              $writeerr = write($guests[$i], $bufs[$b], strlen($bufs[$b])); 

The third step is to broadcast all the incoming messages back to all the users. In this version, each guest will see all the messages. The script goes through the list of guests and writes the same buffer to each socket.

It is easy to develop a more complex messaging system. For instance, a system that allowed private chat messages (individual or group ) could filter messages easily by testing the guest's name against a destination attribute in each message.

 sleep(1);      }   while (!stristr( $buf,"quit") );   closedown(); 

With the sleep() function, we cede execution control of the processor. In this case, we let other processes use the CPU for 1 second between iterations of our loop. Without this sleep, our CPU usage could climb from 0.1% to 85% with no difference in performance. This test for quit is fairly crude since it only tests the most recent string, but it should suffice to get us started.

To put our Chat into action we need only install the swf file and PHP script in the same directory. Multiple users can open the swf file in their browsers and communicate. Typically these users are on separate computers, but for testing puposes there can be two windows on the same machine, as in Figure 20.4.

Figure 20.4. Two Chat Windows Communicating Via the Server


Flash and XML[c] A Developer[ap]s Guide
Flash and XML[c] A Developer[ap]s Guide
ISBN: 201729202
Year: 2005
Pages: 160 © 2008-2017.
If you may any questions please contact us: