POST PART 1
Please take a peek at this socket server that I've written . It's kind of like AMFPHP but for sockets! The basic idea is that it acts as a switchboard for Flash clients so they can talk to each other via socket. In time it should be possible to use this code to write chat applications, multiplayer games, etc. It's a very simple framework that should manage the details of socket code in Flash and php and REALLY help novice programmers by jumping over confusing issues that aren't covered in tutorials anywhere. I hope to augment it with some detailed documentation.

= COMPONENTS =
php socket server - you define classes to add functionality to a socket handler loop
flash actionscript classes - import these into your flash project and most of the gruntwork of socket programming is taken care of

= WHAT IT DOES =
allows you to call functions your server directly from your actionscript code
allows you to call functions on your client directly from your server's php code
automatically serializes and deserializes complex data structures for transit across a socket
simplifies development of multi-user Flash games and chat applications, etc. by providing higher-level programming constructs.

= HOW TO SET IT UP AND RUN THE TEST FILE =
1) download the zip files attached to this topic.
2) unzip flashmog_server.zip to your serve and edit the file and change these constants to config to your server:
FM_HOST - the domain or ip of the server where you run this script
FM_PORT - a port...should be higher than 1024 or higher
* FM_FS_ROOT - the absolute path to this project, with trailing backslash
3) run the socket server thusly (you'll need command-line access to your webserver)

unix_prompt$ flashmog_server.php

NOTE: you may need to change that first line '#!/usr/bin/php -q' to reflect wherever your PHP happens to be installed. You may encounter errors...please bear with me this is super-alpha description here...
4) unzip flashmog_client.zip to your desktop/laptop and open the file FlashMOG_Test.fla using Adobe Flash and hit ctrl-enter

= KNOWN ISSUES =
There are probably tons. I'm looking for comments. In particular
There is an issue with serialization of complex data objects in Flash due to my use of Sephiroth's serialization class. It tends to reverse the order of array elements. I'm working on this.
Potential security issues?
* The list goes on.

    POST PART II
    main socket server code (here)

    #!/usr/bin/php -q
    <?php 
    function flashmog_server_error($err_mesg, $is_fatal, $filename, $line_no) {
      $output = "FlashMOG server error on line " . $line_no . " of file " . $filename . ":\n" . $err_mesg . "\n";
      if ($is_fatal) {
        die($err_mesg . "Execution_halted.\n");
    	} else {
    	  echo($err_mesg);
    	}
    } // flashmog_server_error()
    
    function call_client_method($socket_id, $service_name, $method_name, $args) {
      $rpc_array = array($service_name, $method_name, $args);
      $output = serialize($rpc_array) . chr(0);
    	socket_write($socket_id, $output)
    	  or die('socket_write failed!');
    } // call_client_method()
    
    set_time_limit (0); 
    
    // config this server script
    define('FM_HOST', 'jaith.net');
    define('FM_PORT', 1234);
    define('FM_FS_ROOT', '/home/jaith/public/flashmog/');
    define('FM_MAX_CLIENTS', 10); // the maximum number of clients permitted
    define('FM_SOCKET_BACKLOG', 5); // the maximum number of connections queued
                                    // for processing by socket_listen()
    define('FM_SOCKET_SELECT_TIMEOUT', NULL); // the amount of time socket_select() call
                                              // will block before returning without any
    																					// sockets having changed. NULL means it will
    																					// block indefinitely
    define('FM_SOCKET_READ_LENGTH', 1024); // the number of bytes to be read from a socket
    define('FM_COMMAND_EXIT', 'exit'); // if the only data coming over the socket is this
                                       // string, then close the socket connection to the client
    define('FM_SERIALIZER_METHOD', 'PHP');
    
    switch (FM_SERIALIZER_METHOD) {
      case 'PHP':
        require_once('classes/serialize_php.php');
        break;
      default:
        flashmog_server_error("Unrecognized serialization method: " . FM_SERIALIZE_METHOD, true, __FILE__, __LINE__);
    }
    require_once('classes/flashmog_service.php');
    
    // require_once all classes in the services directory
    $service_class_dir = FM_FS_ROOT . 'services/';
    echo "Including service classes...\n";
    if ($handle = opendir($service_class_dir)) {
      while (false !== ($file = readdir($handle))) {
        if ($file != "." && $file != ".." && !preg_match("#.*\.LCK#", $file)) { // exclude Dreamweaver LCK files
          echo ' ' . $file . "\n";
          require_once($service_class_dir . $file);
        }
      }
      closedir($handle);
    } else {
      flashmog_server_error("Could not read service class directory! ", true, __FILE__, __LINE__);
    }
    
    
    // Create a TCP socket
    $main_listening_socket  =  socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if  (!$main_listening_socket)  {
      $socket_error_code = socket_last_error();
      flashmog_server_error("socket_create() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
    }
    
    if (!socket_set_option($main_listening_socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
      $socket_error_code = socket_last_error($main_listening_socket);
      flashmog_server_error("socket_set_option SO_REUSEADDR failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
    }
    
    // set socket to non-blocking
    if (!socket_set_nonblock($main_listening_socket)) {
      $socket_error_code = socket_last_error($main_listening_socket);
      flashmog_server_error("socket_set_nonblock() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
    }
    
    // Bind the socket to an address/port
    if  (!socket_bind($main_listening_socket, FM_HOST, FM_PORT))  {
      $socket_error_code = socket_last_error($main_listening_socket);
      flashmog_server_error("socket_bind() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
    }
    
    // Start listening for connections 
    if  (!socket_listen($main_listening_socket, FM_SOCKET_BACKLOG))  {
      $socket_error_code = socket_last_error($main_listening_socket);
      flashmog_server_error("socket_listen() failed: " . socket_strerror($socket_error_code), true, __FILE__, __LINE__);
    }
    
    
    $sockets_to_write = array(); // dummy var for passing to socket_select
    $sockets_to_except = array();  // dummy var for passing to socket_select
    
    // Array that will hold client information 
    $clients = array(); 
    
    
    // MAIN LOOP
    while (true) { 
    
      // specify which sockets need listening...should include
      // all of our connected clients
      $sockets_to_read = array();
      $sockets_to_read[0] = $main_listening_socket; 
      foreach ($clients as $client) { 
        if ($client['socket_id'] != null) 
          $sockets_to_read[] = $client['socket_id'];
      } 
    
      // Set up a blocking call to socket_select()...this call alters
      // the $sockets_to_xxxx arrays so that they indicate which sockets
      // need attention
      $num_changed_sockets = socket_select($sockets_to_read, $sockets_to_write, $sockets_to_except, FM_SOCKET_SELECT_TIMEOUT); 
    
      // for some reason, I thought it was a good idea to accept any new clients first
      // if a new connection is being made add it to the client array
      if (in_array($main_listening_socket, $sockets_to_read)) {
        if (sizeof($clients) >= FM_MAX_CLIENTS) {
          echo "rejecting client - max clients already reached\n";
    		} else {
          $new_client = array();
          $new_client['socket_id'] = socket_accept($main_listening_socket);
          $s_host = NULL;
    			$s_port = NULL;
    			socket_getpeername($new_client['socket_id'], $s_host, $s_port);
          echo "new client accepted, host=" . $s_host . ", port=" . $s_port . "\n";
          $new_client['host'] = $s_host;
          $new_client['port'] = $s_port;
          $clients[] = $new_client;
        }
    
    
    // if there was only one socket to deal with, we have dealt with it
    if ($num_changed_sockets == 1) {
      continue; // start main loop again
    }
      } // if socket changed is $main_listening_socket
    
    
    
      // loop through all our clients and deal with those whose socket_ids
      // show up in the $sockets_to_read array
      foreach($clients as $client_key => $client) {
        if (in_array($client['socket_id'] , $sockets_to_read)) {
          $incoming_socket_data = socket_read($client['socket_id'], FM_SOCKET_READ_LENGTH, PHP_BINARY_READ);
          if ($incoming_socket_data === false) {
            // the PHP docs say this means the socket was closed remotely
    				// :TODO: should this be an error instead?
            echo "FALSE received on socket_read(), closing socket id #" . $client['socket_id'] . "\n";
            if (is_resource($client['socket_id'])) {
              socket_close($client['socket_id']);
            }
            unset($clients[$client_key]);
    
      } elseif ($incoming_socket_data === '') {
        // Zero length string meaning disconnected
        echo "zero-length string received on socket_read(), closing socket id #" . $client['socket_id'] . "\n";
        if (is_resource($client['socket_id'])) {
          socket_close($client['socket_id']);
        }
        unset($clients[$client_key]);
    
      } else {
    
        // socket is neither empty nor false....should be real data
        // HANDLE DATA HERE
        $incoming_socket_data = trim($incoming_socket_data);
        if ($incoming_socket_data == FM_COMMAND_EXIT) {
          // requested disconnect
          echo "client " . $client['socket_id'] . "entered 'exit' request, closing\n";
          if (is_resource($client['socket_id'])) {
            socket_close($client['socket_id']);
          }
    
    
        } elseif ($incoming_socket_data) {
          // assume the incoming data is a serialized array of 3 elements
          $rpc_array = Serializer::unserialize($incoming_socket_data);
          if (!$rpc_array) {
            flashmog_server_error("Could not unserialize incoming RPC from socket #" . $client['socket_id'] . "\n" . $incoming_socket_data, false, __FILE__, __LINE__);
          } elseif (!is_array($rpc_array)) {
            flashmog_server_error("Incoming RPC is not an array from socket #" . $client['socket_id'] . "\n" . print_r($rpc_array, true), false, __FILE__, __LINE__);
          } elseif (sizeof($rpc_array) != 3) {
            flashmog_server_error("Incoming RPC has wrong member count from socket #" . $client['socket_id'] . "\n" . print_r($rpc_array, true), false, __FILE__, __LINE__);
          } else {
            // :TODO: we might want to ban certain classes and methods here for security
            list($service_class_name, $method_name, $rpc_parameters) = $rpc_array;
    					if (class_exists($service_class_name)) {
              $service_object = new $service_class_name();
              $service_object->calling_socket_id = $client['socket_id'];
              if (method_exists($service_object, $method_name)) {
                $svc_method_ary = array($service_object, $method_name);
                // :TODO: put try/catch block here to handle exceptions
                ksort($rpc_parameters); // sadly, due to a quirk of the sephiroth serialize in flash
                                   // we must sort the parameters to keep them from being reversed
                call_user_func_array($svc_method_ary, $rpc_parameters);
                // some cleanup
                unset($service_object);
    
              } else {
                flashmog_server_error("Incoming RPC class " . $service_class_name . " doesn't have method " . $method_name . " from socket #" . $client['socket_id'], false, __FILE__, __LINE__);
              } // if method exists
    
            } else {
              flashmog_server_error("Incoming RPC class doesn't exist from socket #" . $client['socket_id'] . "\n" . $service_class_name, false, __FILE__, __LINE__);
            } // if service_class exists
          } // if rpc_array is a propery rpc request array
        } // if socket input...
      } // if socket input is empty string or false
    
    } // if socket in read array
      } // foreach client socket
    } // end while MAIN LOOP
    
    
    if (is_resource($main_listening_socket)) {
      socket_close($main_listening_socket); 
    }
    ?>
    

      POST PART III - related classes and ZIP FILES

      The serialization class in PHP could be change to do XML or whatever, but I'm using basic PHP serialization. The class classes/serialize_php.php is very simple:

      <?php 
      //****************************************************************************
      // written by j. adams .  Copyright TBD.
      //****************************************************************************
      
      class Serializer {
        static function serialize($data) {
          return serialize($data);
        }
      	static function unserialize($serialized_data) {
          return unserialize($serialized_data);
        }
      } // class Serializer
      
      
      ?>
      

      flashmog_service.php is also simple - at this point it's just a base class to store the socket resource id of the socket that is currently being handled

      <?php 
      //****************************************************************************
      // written by j. adams .  Copyright TBD.
      //****************************************************************************
      
      class FlashMOGService {
        public $calling_socket_id = NULL; // the resource ID of the socket that called
                                       // a method of this class, thereby forcing
                                       // it to be instantiated
      
      } // class FlashMOGService
      ?>
      

      And finally, I have written a class to instantiate a Service class on the server...just for proof of concept and testing:

      <?php
      //****************************************************************************
      // written by j. adams.  Copyright TBD.
      //****************************************************************************
      
      // this class is just to illustrate how flashMOG server methods are created.
      
      class myService extends FlashMOGService {
        function serverMethod1($textArg, $numberArg) {
      
      $arg1 = 'This is the argument, defined originally in serverMethod1 of my PHP class myService';
      $params = array($arg1);
      call_client_method($this->calling_socket_id, get_class($this), 'firstClientMethod', $params);
        } // serverMethod1()
      
        function serverMethod2($arrArg) {
          $arg1 = 'This is the first argument, defined serverMethod1.  The second arg should be the array passed here from Flash';
          $params = array($arg1, $arrArg);
          call_client_method($this->calling_socket_id, get_class($this), 'secondClientMethod', $params);
      
        } // serverMethod2()
      
      } // class myService
      ?>
      

      QUESTIONS?
      Would it be possible (and possibly faster) to instantiate a single instance of each service class? Having borrowed most of the socket server concepts from tutorials, I'm not sure if I risk multithreaded execution situations...it's important that each method executed on the server know which client is requesting it.
      How to get access to the complete list of $clients in each Service class. Should it be passed as global? stored by reference in each instantiation of a particular service class?
      * the list goes on and on. phew...

      comments? all are welcome.

        19 days later

        Man, this looks like a great idea. I've been searching for something like this off and on for months. But one key... I am looking for something that will allow for the 2 clients to not rely on a server AFTER an initial connection is setup.... something that will connect the 2 clients (both which may be behind firewalls-- ie neither have their own world-recognizable/direct IP). Will this do this? Dowloaded now... can't wait to try it out. I'm a PHP developer too, so let me know if you want help.

          I'm not sure that's possible with Flash. I'm thinking Flash is not capable of setting up a 'listening' socket. Don't take my word for it though. I could be wrong.

          Here's the latest source code...I've done some testing and debugging. There are some basic classes for testing purposes in the services folder.

            thanks for the quick reply. Do you know how I can find this out or what to search for to do this?

            I was thinking you would need to have a directory server that would sort of manage the connections and be the central directory (that's where your php script would initially be used). Then, I am trying to imagine this... you would need to "pass off" the connection to the clients.

            userA = x.x.x.x
            userB = y.y.y.y

            userA connects to main server via socket
            userB connects to main server via socket

            userA requests to connect to userB via the server.

            At this point, I need a better understanding of how networking works. It would seem to me that for this to work that a different IP would need to "take over" the connection? Otherwise, like you said, Flash would need some way to sit and listen for the connection. On top of that, if neither computers had a world readable IP, there would be no way of one to know where the other is.

            However, the little I know about networking, I was under the impression that for example, when a user behind a router requests a webpage from a web server, the web server responds to that IP (which is the router). Then the router "knows" and routes the right web traffic to the right user. I think this is done via a dynamically assigned high port in the router. If this is all the case, it would seem to me maybe there's a way to put a new server "in charge" of responding to this high port.

            This is done all the time, for example, when a webpage is redirected, right? A user requests a page from php.net but the page on php.net can call a header("Location: http://www.google.com")... a redirect and it is google who then is directly servicing the page request and responding to the port number that php.net was supposed to respond to. So, in effect, google replied to the high port which was supposed to have responded to by php.net.

            Do you follow my logic?

              wgardner wrote:

              At this point, I need a better understanding of how networking works. It would seem to me that for this to work that a different IP would need to "take over" the connection? Otherwise, like you said, Flash would need some way to sit and listen for the connection. On top of that, if neither computers had a world readable IP, there would be no way of one to know where the other is.

              I need a better understanding too. Neither PHP nor Flash docs are very detailed regarding sockets. As far as I know, there is no way thru either coding language to alter an existing socket in that way. Also, there appears to be no actionscript command to accept an incoming socket request. I think you'll need the server to continue to act as a middle man to route packets.

              wgardner wrote:

              However, the little I know about networking, I was under the impression that for example, when a user behind a router requests a webpage from a web server, the web server responds to that IP (which is the router). Then the router "knows" and routes the right web traffic to the right user. I think this is done via a dynamically assigned high port in the router. If this is all the case, it would seem to me maybe there's a way to put a new server "in charge" of responding to this high port.

              If you read source code of this project, you see that I create a listening socket in php that listens on a particular port (.e.g, 1234). This socket is where the flash clients try and connect: mydomain.com:1234 When they do connect, I believe the server chooses some arbitrary higher port number as the channel of communication for the newly connected client. Each client, when connecting gets it's own communication channel on some random port.

              wgardner wrote:

              This is done all the time, for example, when a webpage is redirected, right? A user requests a page from php.net but the page on php.net can call a header("Location: http://www.google.com")... a redirect and it is google who then is directly servicing the page request and responding to the port number that php.net was supposed to respond to. So, in effect, google replied to the high port which was supposed to have responded to by php.net.

              Do you follow my logic?

              I follow your logic, but I don't know enough about it. I don't think you're going to find a way to do this with flash. You'll probably need some other programming language that has more robust socket functionality. Like a C++ or java library or something.

                24 days later

                If anyone is interested, I have created a site for FlashMOG along with code download, quick start guide, source documentation, and community forum. I'm still working on the detailed documentation.

                http://flashmog.net

                  Write a Reply...