Page 1 of 2

Sockets - Client closing connection

Posted: Sun Feb 15, 2009 7:13 pm
by Biganon
Hi everybody,

I've got a problem with a PHP script I'm trying to make work (sorry for my strange language, I'm Swiss :roll: )

Here is my code (http://paste-it.net/public/sae1180/ to see it better) :

Code: Select all

<?php
set_time_limit(0);
$address = '87.98.146.113';
$port = 17622;
 
if(($creation = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "Unable to create the socket : "."socket_strerror(socket_last_error())"."\n";
    exit();
} else {
    echo "Creation ok.\n";
}
 
if(($option = socket_set_option($creation, SOL_SOCKET, SO_REUSEADDR, 1)) === false) {
        echo "Unable to modifiy the option SO_REUSEADDR : ".socket_strerror(socket_last_error())."\n";
    exit();
} else {
        echo "Modification ok.\n";
}
 
if(($binding = socket_bind($creation, $address, $port)) === false) {
    echo "Unable to bind the socket : ".socket_strerror(socket_last_error())."\n";
    exit();
} else {
    echo "Binding ok.\n";
}
 
if(($listening = socket_listen($creation,10)) === false) {
        echo "Unable to listen to the socket : ".socket_strerror(socket_last_error())."\n";
    exit();
} else {
        echo "Listening ok.\n";
}
 
$real_array = array($creation);
while(true) {
    $sockets = $real_array;
    socket_select($sockets, $reading = NULL, $except = NULL, NULL);
    foreach($sockets as $sock) {
        if($sock == $creation) {
            if(($client = socket_accept($sock)) === false) {
                echo "Unable to accept the client : ".socket_strerror(socket_last_error())."\n";
                exit();
            } else {
                echo "Client accepted.\n";
            }
            array_push($real_array,$client);
        } else {
            socket_recv($sock, $buffer, 2048, 0);
            echo "Message sent : $buffer\n";
            $broadcast_array = $real_array;
            array_shift($broadcast_array);
            envoi_message($broadcast_array,$buffer);
        }
    }
}
 
function envoi_message($destis,$content) {
    foreach($destis as $desti) {
        @socket_write($desti,$content);
    }
}
?>
(I had to translate my variables name for you to understand their aim better... :P)

It's an adaptation I made of a code I found in a PDF written by Thibault Imbert, called "Pratique d'ActionScript 3".

As you can see, it's a socket server which listens to clients (my clients are Flash chat applications) and broadcasts what it receives to all the other clients connected.

I launch it (using screen, to keep it alive) with

Code: Select all

php -f socket3.php
on my Ubuntu Server.

It works perfect. BUT there is one problem, which bores me. I'll try to describe the events chronologically :

1) I launch the server with the command I wrote above => The terminal tells me it's launched, listening, binded, everything.
2) I connect with the Flash client, pick a nickname => The terminal tells me : "Client accepted".
3) I send a few messages with the Flash client => The XML nodes containing the message and the nickname appear on the terminal.
4) I close the Flash client => The terminal displays :
"Message sent :
Message sent :
Message sent :
Message sent :
..."
Again and again, an infinity of "Message sent : " !!
To avoid the terminal to display that, I first put an "if" which checked if $buffer was NULL. If it was, nothing was done. With it, everything went well, for the client and for the terminal.

But something is telling me that it's not normal... And indeed, the problem is clear : I do not "remove" the clients that are not connected anymore ! With my modifications it's doesn't appear to be very serious, but when I imagine many people connecting and the server having being running for a long long time, I guess that it will get much slower ! And it could even crash ! Because ALL the clients will be kept by the server ! So I want it to be cleaner, to remove clients that are not connected anymore.

And that's why I'm posting : I don't know how to do.

I tried to do this : Remember my "if" that checked if $buffer was NULL ? Well, I tried to make that, when the $buffer was NULL, then the client HAD to be disconnected. Why ? Because a NULL $buffer happened only in that case. Even with an empty nickname and en empty message, the XML structure of the broadcast string still appears, so it's not NULL.
I thought it could be the solution... But I couldn't make it work. Here's what I did : when the $buffer was NULL, I just did socket_shutdown and then socket_close to the $sock on which the foreach loop was working.

But then I got that message on the terminal, when closing the Flash client :
Warning: socket_select(): 5 is not a valid Socket resource in /home/pluton/socket3.php on line 37
So I don't know what to do :( And I think my method (checking if $buffer is NULL) is not a good method...

How would you manage to remove disconnected users, for $real_array to contain the EXACT connected clients, and no ghosts (except during a few seconds, the lag doesn't matter at all) ?

Thank you very much :D

Re: Sockets - Client closing connection

Posted: Sun Feb 15, 2009 10:54 pm
by s.dot
I remember I had this problem when trying to write a php socket chat server. You will need a check to see if the client is still connected.. if they are not connected you have to remove them from the list of clients to be sent messages to (which I believe is $sockets in your script). Try using array_push() on the client to remove them and then breaking to reset the loop.

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 7:05 am
by Biganon
Thank you for your answer. I know that's what I've gotta do, but I don't know how to do it :(

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 4:14 pm
by Weirdan
You can tell if client disconnected by return value of socket_recv and $buffer set to null: http://us2.php.net/manual/en/function.s ... .php#76699.

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 6:30 pm
by Biganon
Thank you Weirdan, this answers one of my questions. But I'm still unable to remove the disconnected user from the array :\

Here's my actual code :

Code: Select all

<?php
set_time_limit(0);
$adresse = '87.98.146.113';
$port = 17622;
 
if(($creation = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
   echo "Impossible de créer la socket : "."socket_strerror(socket_last_error())"."\n";
   exit();
} else {
   echo "Création de la socket réussie.\n";
}
 
if(($option = socket_set_option($creation, SOL_SOCKET, SO_REUSEADDR, 1)) === false) {
        echo "Impossible de modifier l'option SO_REUSEADDR : ".socket_strerror(socket_last_error())."\n";
   exit();
} else {
        echo "Modification de SO_REUSEADDR réussie.\n";
}
 
if(($liaison = socket_bind($creation, $adresse, $port)) === false) {
   echo "Impossible de lier la socket : ".socket_strerror(socket_last_error())."\n";
   exit();
} else {
   echo "Liaison de la socket réussie.\n";
}
 
if(($ecoute = socket_listen($creation,10)) === false) {
        echo "Impossible d'écouter la socket : ".socket_strerror(socket_last_error())."\n";
   exit();
} else {
        echo "Ecoute de la socket réussie.\n";
}
 
$tab_utile = array($creation);
while(true) {
   $sockets = $tab_utile;
   socket_select($sockets, $lecture = NULL, $except = NULL, NULL);
   foreach($sockets as $sock) {
      if($sock == $creation) {
         if(($client = socket_accept($sock)) === false) {
            echo "Impossible d'accepter le client : ".socket_strerror(socket_last_error())."\n";
            exit();
         } else {
            echo "Client accepté.\n";
         }
         array_push($tab_utile,$client);
      } else {
         socket_recv($sock, $buffer, 2048, 0);
         if($buffer != NULL) {
            echo "Message transmis : $buffer\n";
            $tab_envois = $tab_utile;
            array_shift($tab_envois);
            envoi_message($tab_envois,$buffer);
         } else {
            socket_shutdown($sock,2);
            socket_close($sock);
            foreach ($sockets as $key =>$sock) {
                 unset($array[$key]);
            }
         }
      }
   }
}
 
function envoi_message($destis,$contenu) {
   foreach($destis as $desti) {
      @socket_write($desti,$contenu);
   }
}
?>

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 6:53 pm
by Weirdan
I think this is because you're removing it from $sockets array which is being rewritten (by copying from $tab_utile array) on the next iteration of the while loop. I guess removing client connection from $tab_utile array instead should solve this problem.

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 8:18 pm
by Biganon
Thank you, but with

Code: Select all

 
foreach ($tab_utile as $key =>$sock) {
          unset($tab_utile[$key]);
}
I get
Warning: socket_select(): no resource arrays were passed to select in /home/pluton/socket3.php on line 37
, so another warning, and this one is flooded in the terminal...

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 8:29 pm
by Weirdan
you weren't supposed to unset entire $tab_utile array :lol: , only those client connections that are disconnected:

Code: Select all

 
foreach ($sockets as $key => $sock) {
   if ($sock == $creation) {
        // accept stuff goes here
   } else {
        socket_recv($sock, $buffer, 2048, 0);
        if ($buffer !== null) {
            // broadcast stuff goes here
        } else {
             echo 'Client closed connection' . PHP_EOL;
             socket_shutdown($sock, 2);
             socket_close($sock);
             unset($tab_utile[$key]);
        }
   }
}
 

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 8:41 pm
by Biganon
I'm sorry but I still get the same warning flooded :banghead: :D

Here is my while loop :

Code: Select all

while(true) {
        $sockets = $tab_utile;
        socket_select($sockets, $lecture = NULL, $except = NULL, NULL);
        foreach($sockets as $key => $sock) {
                if($sock == $creation) {
                        if(($client = socket_accept($sock)) === false) {
                                echo "Impossible d'accepter le client : ".socket_strerror(socket_last_error())."\n";
                                exit();
                        } else {
                                echo "Client accepté.\n";
                        }
                        array_push($tab_utile,$client);
                } else {
                        socket_recv($sock, $buffer, 2048, 0);
                        if($buffer !== NULL) {
                                echo "Message transmis : $buffer\n";
                                $tab_envois = $tab_utile;
                                array_shift($tab_envois);
                                envoi_message($tab_envois,$buffer);
                        } else {
                                socket_shutdown($sock,2);
                                socket_close($sock);
                                unset($tab_utile[$key]);
                        }
                }
        }
}
 

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 8:58 pm
by Weirdan
what is the value of $key when socket is being closed? And what is your php version? (old versions did not preserve array keys in socket_select())

Re: Sockets - Client closing connection

Posted: Mon Feb 16, 2009 9:07 pm
by Biganon
PHP Version 5.2.4-2ubuntu5.5

I put an "@" before socket_select to stop being flooded, and I could see that other Warnings were coming before, here is the complete log, the information you asked is after the ">>>>>>>>>" :
Création de la socket réussie.
Modification de SO_REUSEADDR réussie.
Liaison de la socket réussie.
Ecoute de la socket réussie.
Client accepté.
Message transmis : <root><messag pseudo="Biga" contenu="lol" /></root>
>>>>>>>>>>>>>> 0
Warning: socket_recv(): 5 is not a valid Socket resource in /home/pluton/socket4.php on line 49

Warning: socket_shutdown(): 5 is not a valid Socket resource in /home/pluton/socket4.php on line 56

Warning: socket_close(): 5 is not a valid Socket resource in /home/pluton/socket4.php on line 57
>>>>>>>>>>>>>> 1

Re: Sockets - Client closing connection

Posted: Tue Feb 17, 2009 1:24 pm
by Biganon
No idea ? :|

Re: Sockets - Client closing connection

Posted: Tue Feb 17, 2009 2:41 pm
by Weirdan
Biganon wrote:No idea ? :|
Have to sleep and work sometimes :). The problem is with 0th socket, which should have never been closed.

Try dumping array_keys($sockets) on every iteration of the while loop - if there will never be any 'holes' (meaning missing integers in sequence, like here: 0 1 4 7) - then the problem is with your PHP interpreter or OS. If it's the case then there are workarounds available in user comments on this page: http://us2.php.net/socket_select

Re: Sockets - Client closing connection

Posted: Tue Feb 17, 2009 3:46 pm
by Biganon
Yep sorry, Switzerland is not at the same hour that Ukraine I think^^

Here's what I get with var_dump(array_keys($sockets)); in the while loop :

First I get this when I connect and send one message with the client :
Création de la socket réussie.
Modification de SO_REUSEADDR réussie.
Liaison de la socket réussie.
Ecoute de la socket réussie.
Client accepté.
array(1) {
[0]=>
int(0)
}
Message transmis : <root><messag pseudo="Biga" contenu="Hello" /></root>
array(1) {
[0]=>
int(0)
}
And then when I close the client, I'm flooded with

Code: Select all

}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
 

And with $tab_utile instead of $sockets :
Création de la socket réussie.
Modification de SO_REUSEADDR réussie.
Liaison de la socket réussie.
Ecoute de la socket réussie.
Client accepté.
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}
Message transmis : <root><messag pseudo="Biga" contenu="lol" /></root>
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}
and then
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
}
array(0) {
So I don't know if there is any "hole" :|

Thank you, ++

Re: Sockets - Client closing connection

Posted: Tue Feb 17, 2009 4:33 pm
by Weirdan
It seems you're affected by the problem of socket_select isn't being able to maintain array keys. I have an idea: since resources support equality comparison, you may use array_search to locate socket for removal:

Code: Select all

 
foreach ($sockets as $key => $sock) {
   if ($sock == $creation) {
        // accept stuff goes here
   } else {
        socket_recv($sock, $buffer, 2048, 0);
        if ($buffer !== null) {
            // broadcast stuff goes here
        } else {
             $key_for_removal = array_search($sock, $tab_utile);
             if ($key_for_removal === false) {
                 echo 'Cannot locate client connection to remove' . PHP_EOL;
             } else {
                 unset($tab_utile[$key_for_removal]);
             }
             echo 'Client closed connection' . PHP_EOL;
             socket_shutdown($sock, 2);
             socket_close($sock);
        }
   }
}