Sockets - Client closing connection

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Sockets - Client closing connection

Post 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
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Sockets - Client closing connection

Post 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.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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 :(
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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.
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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);
   }
}
?>
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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.
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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...
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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]);
        }
   }
}
 
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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]);
                        }
                }
        }
}
 
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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())
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post by Biganon »

No idea ? :|
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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
Biganon
Forum Newbie
Posts: 9
Joined: Sun Feb 15, 2009 7:10 pm

Re: Sockets - Client closing connection

Post 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, ++
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Sockets - Client closing connection

Post 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);
        }
   }
}
Post Reply