tags:

views:

346

answers:

2

I recently found out that PHP not only has the fsock* functions, but also functions to create a server itself. I decided to experiment a little bit, and came up with this. Now, the problem is that it hangs on accept_connection() (due to the fact that it's waiting for a connection.) I found out that the solution is to use stream_set_blocking(), which as you can see, I attempted, but to no avail.

I am getting an error message, which reads:

Warning: socket_set_blocking(): supplied resource is not a valid stream resource in /home/insomniaque/workspace/PHP Socket RAT/rat.class.php on line 68

I know that accept_connection() was the problem earlier because when I would connect with a second connection, it would output the data.

<?php
/*
 * Project: iRAT
 * 
 * Created on Jan 11, 2010
 * Written by Insomniaque
 * 
 */

class rat
{
    /**
     * Holds the PHP socket handle for use within the class.
     */
    private $socket;

    /**
     * Holds an array of all the spawned sockets (child sockets) that were
     * created when a user connected to the server.
     */
    private $spawns = array ();

    /**
     * Holds the maximum number of connections.
     */
    private $maxconn;

    /**
     * Sets all of the variables required for the class and starts the socket.
     * Then it'll start looping, connecting clients and running commands.
     * 
     * @access public
     * @param $port The port to bind.
     * @param $maxconn The maximum number of client connections.
     */
    public function __construct($port = 0, $maxconn = 1)
    {
        /**
         * Check to see if the user has entered 0 as the port, and create a
         * random port, if so.
         */
        if($port == 0)
            $this->port = rand(81, 8079);
        else
            $this->port = $port;

        /**
         * Save the maximum connection number.
         */
        $this->maxconn = $maxconn;

        /**
         * Run our function to create the socket now.
         */
        if(!$this->createSocket())
        {
            echo "Failed creating or binding socket.\n";
            return false;
        }
        else
        {
            echo "Socket has been created and binded.\n";
        }

        /**
         * Turn non-blocking on so we can run multiple clients.
         */
        socket_set_blocking($this->socket, 0);

        echo "Starting the data receiving loop.\n";
        $this->startLoop();

        return true;
    }

    /**
     * This function will create the socket for later use.
     * 
     * @access private
     * @return bool Returns true if the socket was created successfully,
     *              returns false if there was an error.
     */
    private function createSocket()
    {
        /**
         * Create a socket of IPv4 type using the TCP gateway.
         */
        $this->socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
        if(!$this->socket)
            return false;
        echo "Socket has been created.\n";

        /**
         *  Attempt to bind the socket to localhost:[port]
         */
        do
        {
            if(!isset($i))
                $i++;

            $port = $this->port;
            $bind = socket_bind($this->socket, 0, $port);

            if(!$bind)
            {
                $i++;
                $this->port = rand(79, 8079);
            }
        } while(!$bind && $i <= 5);

        if($i == 5)
            return false;
        echo "Port ".$this->port." has been binded to the RAT.\n";

        return true;
    }

    /**
     * Start a loop running on our socket. We will check if we are getting
     * data, accept connections and run any commands. When the connection
     * is closed, we will return true.
     * 
     * @access private
     * @return bool Returns false if the socket can't be listened to. Otherwise
     *              returns true when the socket is closed.
     */
    private function startLoop()
    {
        if(socket_listen($this->socket, 3))
        {
            while(true)
            {   
                if(($newspawn = socket_accept($this->socket)) !== false)
                {
                    $this->spawns[] = $newspawn;
                    echo "A new spawn has connected.";
                } else
                    echo "No new socket";
                sleep(1000);

                foreach($this->spawns as $key => $spawn)
                {
                    $data = trim(socket_read($spawn, 1024));
                    if(strlen($data) > 0)
                    {
                        if($data == "exit")
                        {
                            socket_close($spawn);
                            unset($this->spawns[$key]);
                            echo "Spawn killed.\n";
                        }
                        if($data == "kill")
                        {
                            foreach($this->spawns as $key => $spawn)
                            {
                                socket_close($spawn);
                                unset($this->spawns[$key]);
                            }
                            socket_close($this->socket);
                            echo "Socket closed.\n";
                            return true;
                        }
                        else
                        {
                            echo "Data: " . $data . "\n";
                        }
                    }
                }
            }
        }
        else
        {
            echo "Failure receiving data.\n";
            return false;
        }
    }
}
?>

Thanks in advance, John

+1  A: 

I believe you would be interested in using stream_socket_create() as opposed to socket_create(), which should return a valid resource to be used with socket_set_blocking().

Note that you will also need to utilize stream_socket_accept() to begin accepting connections on your newly created socket.

cballou
I switched everything over to this and tried it (I accidentally lost the code now,) and it would allow one client to connect, and then the whole thing hung, even when I tried connecting a new user.
John
@John - Try placing `socket_set_blocking` on the stream resource returned from `stream_socket_accept` in the while loop.
cballou
@cballou: http://paste2.org/p/606125 - `stream_set_blocking()` returns TRUE on success and FALSE on failure.
John
Also, I realized I made a mistake, and change it to do a `stream_set_blocking($this->socket, 0);` just after the while(true), and simply use $this->socket for the `stream_accept_connection()`. It now hangs after the connection connects, and doesn't even return data when you connect the second account.
John
+1  A: 

Use socket_set_nonblock() instead of socket_set_blocking() for a socket resource create by socket_create().

socket_set_blocking() is an alias for stream_set_blocking() which only works for (php-)streams, like the result of fopen() or stream_socket_create()

edit: You can also use socket_select() or stream_select() to handle both new connections and client data packet, e.g.

private function createSocket()
{
  $this->socket = stream_socket_server('tcp://0.0.0.0:'.(int)$this->port, $errno, $errstr);
  if(!$this->socket) {
    $this->socket = null;
    echo "stream_socket_server failed : $errstr ($errno)\n";
    return false;
  }
  echo "Port ".$this->port." has been bound to the RAT.\n";
  return true;
}

public function startLoop() {
  if ( is_null($this->socket) ) {
    return false;
  }

  $write = array(); $exception=array();
  while( !$this->shutdown ) {
    // stream_select() alters the array, so $read has to be re-constructed in each iteration somehow
    $read = array_merge(array($this->socket), $this->spawns);
    // you might want to check $exception as well
    if ( 0!==stream_select($read, $write, $exception, 4) ) {
      // $now $read only contains those sockets, that will not block
      // for fread/accept operations
      foreach($read as $s) {
        if ( $s===$this->socket ) {
          $this->onAccept();
        }
        else {
          $this->onClientPacket($s);
        }
      }
    }
  }
  $this->shutdown();
}

Keep in mind that if a client sends two commands like e.g.

$fp1 = stream_socket_client("tcp://localhost:81", $errno, $errstr, 30);
fwrite($fp1, "1 blabla");
fwrite($fp1, "exit");

it doesn't necessarily mean your server will get two separate messages.

VolkerK
correct, as `socket_set_blocking()` is simply an alias for `stream_set_blocking()`
cballou
I'm a slow-typer around 4am in the morning and pressed "Save" after the first paragraph ;-)
VolkerK
Okay, I tried this (as I had used before switching when I found out it linked to `socket_set_blocking()`,) and I get this error:`Warning: socket_accept(): unable to accept incoming connection [11]: Resource temporarily unavailable in /home/insomniaque/workspace/PHP Socket RAT/rat.class.php on line 133`.
John
(untested) example added. I guess you also have a problem with your client/server "protocol".
VolkerK
Thanks, I'll try it when I get home.
John
After going through the code, I almost have it working. I connected through localhost 6672 (random port) and it was all working fine. When I typed "hello" it said "User said hello to the server." When I typed anything else, it would be printed out. The problem was, I opened up a second Terminal and tried to connect again. I got a connection refused error. I tried to have a friend connect, thinking it could just be a problem of two of the same IP. he said that he was disconnected, and my terminal never displayed a new connection for him. Here's the code: http://paste2.org/p/607687
John
Each time stream\_select() returns you're looping through all $spawns sockets and call fread() for each of them. If there's no data for at least one socket (which is very likely) fread() will block until there's at least one byte of data (another packet). Take a look at my example. It only loops through $read. When stream\_select returns $read only contains those sockets that will not block with accept/fread.
VolkerK
Thanks, I'll try this very soon.
John
Alright, now everything works until a user connects. No data is showing up on the server side (other than the "Accepted new connection" message.) I tried connecting through a second terminal and it showed another "Accepted new connection" message but telnet said the Connection was refused. I noticed that the other telnet had been closed as well. When I tried re-connecting, it was fine. So I'm trying to figure out why no data is being sent, and why connecting a second user doesn't work AND kicks the first user off. http://paste2.org/p/609341
John
`$spawns[] = stream_socket_accept($this->socket);` should be `$this->spawns = ... ` or even better `$n=stream_socket_accept(...); if ($n) $this->spawns[] = $n;`. Also take a closer look at `fread($spawns[$i], 1024)`, there is no $i, you're using a foreach-loop.
VolkerK
Ah, nice catch with the $i, thanks. I'll go try this.
John
Did a bit more tweaking and got it working. Thanks a lot.
John