Re: HELP! Socket server w/multiple connections
| From: | Tom Rogers | Date: | Mon, 26 Apr 2004 23:05:07 +0000 |
| Subject: | Re: HELP! Socket server w/multiple connections | ||
| References: | 1 | Groups: | php.general |
| Request: | Send a blank email to php-general+get-184704@lists.php.net to get a copy of this message | ||
Hi,
Tuesday, April 27, 2004, 6:26:52 AM, you wrote:
RF> Hi Mike,
RF> I'm at a point where a little direction would be really appreciated.
RF> I've gone through several tutorials for socket servers and clients.
RF> I've got a web form submitting data to my socket server, and it works.
RF> I think I even understand what's going on. So far, so good.
RF> Now comes the hard part: I need to write a socket server that stays
RF> running (easy) and can handle and keep track of multiple connections
RF> (hard, for me). Now, I've found several tutorial code snippets, but no
RF> matter how much editing I do to them, when I run them from the command
RF> line, they don't work. It seems like there is a huge gulf of expertise
RF> separating a single-connection socket server with one that handles
RF> multiple connections concurrently. And no matter which example I use, I
RF> can't get them to run. With the first socket server I tried, I got this
RF> error message:
RF> PHP Notice: Undefined offset: 0 in
RF> /Users/me/Sites/sockets/test/s9.php on line 21
RF> Notice: Undefined offset: 0 in
RF> /Users/me/Sites/sockets/test/s9.php on
RF> line 21
RF> PHP Notice: Undefined offset: 1 in
RF> /Users/me/Sites/sockets/test/s9.php on line 21
RF> Notice: Undefined offset: 1 in
RF> /Users/me/Sites/sockets/test/s9.php on
RF> line 21
RF> PHP Notice: Undefined offset: 2 in
RF> /Users/me/Sites/sockets/test/s9.php on line 21
RF> Here's the code it doesn't like:
RF> // Setup clients listen socket for reading
RF> $read[0] = $sock;
RF> for ($i = 0; $i < $max_clients; $i++)
RF> {
RF> if ($clients[$i]['sock'] != $null) $read[$i+1] = $clients[$i]['sock'];
RF> }
RF> Here's the original code, in full:
RF> http://www.zend.com/zend/tut/tutorial-staub3.php
RF> After struggling with this, I found another multi-connection socket
RF> server sample which looks even stronger on paper, but fails to do
RF> anything. At the command line, I execute it, and it runs without errors
RF> and immediately returns to command line. Here's a link to the code:
RF> http://www.zend.com/lists/php-dev/200205/msg00286.html
RF> Here's my code verbatim:
RF> ---------
RF> <?php
RF> // VARIABLES
RF> set_time_limit(0);
RF> error_reporting(E_ALL);
RF> // defaults...
RF> define('MAXLINE', 1024); // how much to read from a socket at a
RF> time
RF> define('LISTENQ', 10); // listening queue
RF> define('PORT', 10000); // the default port to run on
RF> define('FD_SETSIZE', 5); // file descriptor set size (max number
RF> of concurrent clients)...
RF> // for kill the biatch...
RF> function killDaemon()
RF> {
RF> global $listenfd, $client;
RF> socket_close($listenfd);
RF> $msg = "Daemon going down!\n";
RF> for ($i = 0; $i < FD_SETSIZE; $i++)
RF> {
RF> if ($client[$i] != null)
RF> {
RF> socket_write($client[$i], $msg, strlen($msg));
RF> socket_close($client[$i]);
RF> }
RF> }
RF> print "Shutting down the daemon\n";
RF> exit;
RF> }
RF> // whenever a client disconnects...
RF> function closeClient($i)
RF> {
RF> global $client, $remote_host, $remote_port;
RF> print "closing client[$i]
RF> ({$remote_host[$i]}:{$remote_port[$i]})\n";
RF> socket_close($client[$i]);
RF> $client[$i] = null;
RF> unset($remote_host[$i]);
RF> unset($remote_port[$i]);
RF> }
RF> // set up the file descriptors and sockets...
RF> // $listenfd only listens for a connection, it doesn't handle anything
RF> // but initial connections, after which the $client array takes over...
RF> $listenfd = socket_create(AF_INET, SOCK_STREAM, 0);
RF> if ($listenfd)
RF> print "Listening on port " . PORT . "\n";
RF> else
RF> die("AIEE -- socket died!\n");
RF> socket_setopt($listenfd, SOL_SOCKET, SO_REUSEADDR, 1);
RF> if (!socket_bind($listenfd, "0.0.0.0", PORT))
RF> {
RF> socket_close($listenfd);
RF> die("AIEE -- Couldn't bind!\n");
RF> }
RF> socket_listen($listenfd, LISTENQ);
RF> // set up our clients. After listenfd receives a connection,
RF> // the connection is handed off to a $client[]. $maxi is the
RF> // set to the highest client being used, which is somewhat
RF> // unnecessary, but it saves us from checking each and every client
RF> // if only, say, the first two are being used.
RF> $maxi = -1;
RF> for ($i = 0; $i < FD_SETSIZE; $i++)
RF> $client[$i] = null;
RF> // the main loop.
RF> while(1)
RF> {
RF> $rfds[0] = $listenfd;
RF> for ($i = 0; $i < FD_SETSIZE; $i++)
RF> {
RF> if ($client[$i] != null)
RF> $rfds[$i + 1] = $client[$i];
RF> }
RF> // block indefinitely until we receive a connection...
RF> $nready = socket_select($rfds, $null, $null, null);
RF> // if we have a new connection, stick it in the $client array,
RF> if (in_array($listenfd, $rfds))
RF> {
RF> print "listenfd heard something, setting up new client\n";
RF> for ($i = 0; $i < FD_SETSIZE; $i++)
RF> {
RF> if ($client[$i] == null)
RF> {
RF> $client[$i] = socket_accept($listenfd);
RF> socket_setopt($client[$i], SOL_SOCKET, SO_REUSEADDR, 1);
RF> socket_getpeername($client[$i], $remote_host[$i],
>>$remote_port[$i]);
RF> print "Accepted
RF> {$remote_host[$i]}:{$remote_port[$i]} as
>>client[$i]\n";
RF> break;
RF> }
RF> if ($i == FD_SETSIZE - 1)
RF> {
RF> trigger_error("too many clients", E_USER_ERROR);
RF> exit;
RF> }
RF> }
RF> if ($i $maxi)
RF> $maxi = $i;
RF> if (--$nready <= 0)
RF> continue;
RF> }
RF> // check the clients for incoming data.
RF> for ($i = 0; $i <= $maxi; $i++)
RF> {
RF> if ($client[$i] == null)
RF> continue;
RF> if (in_array($client[$i], $rfds))
RF> {
RF> $n = trim(socket_read($client[$i], MAXLINE));
RF> if (!$n)
RF> closeClient($i);
RF> else
RF> {
RF> // if a client has sent some data, do one of these:
RF> if ($n == "/killme")
RF> killDaemon();
RF> else if ($n == "/quit")
RF> closeClient($i);
RF> else
RF> {
RF> // print something on the server, then echo the
RF> incoming
RF> // data to all of the clients in the $client array.
RF> print "From
RF> {$remote_host[$i]}:{$remote_port[$i]},
>>client[$i]: $n\n";
RF> for ($j = 0; $j <= $maxi; $j++)
RF> {
RF> if ($client[$j] != null)
RF> socket_write($client[$j], "From client[$i]:
>>$n\r\n");
RF> }
RF> }
RF> }
RF> if (--$nready <= 0)
RF> break;
RF> }
RF> }
RF> }
?>>
RF> Any ideas???
You should probably look at socket_select() if you want to run in a
continuous while loop and set your sockets to non blocking.
Here is a function I use as debug listner which will be of no use to
you directly but shows one way of using socket_select in a loop (this
is running under windows which has dismal socket support so the same
idea under unix should be a breeze :)
//This sets up the sockets
function &getSocket($address='',$port=0,$listen=false){
//$sock =& $GLOBALS['socket'];
//echo "Opening socket\n";
if (($sock = socket_create(AF_INET, SOCK_STREAM, 0)) < 0) {
//echo "socket_create() failed: reason: " . socket_strerror ($sock) .
"\n";
}
if($port > 0){
if (($ret = socket_bind($sock, $address, $port)) < 0) {
echo "socket_bind() failed: reason: " .
socket_strerror ($ret) . "\n";
}
}
if($listen){
if (($ret = socket_listen($sock, 5)) < 0) {
echo "socket_listen() failed: reason: " .
socket_strerror ($ret) . "\n";
}
}
socket_set_nonblock($sock);
//echo "returning $sock \n";
return $sock;
}
//this function does an http request on one socket then listens on
//another for debug output until the remote ends or I press stop
//this is running as a cli process with php-gtk so ignore those bits
function getHttp($server='',$page='',$mode='dbg'){
$sid=177562;
$sock;
$url_sock;
$msgsock;
$dbg_port = 7878;
$tosend = array();
$toget = array();
$post = '';
$r = array(
'state'=>'disconnect',
'error'=>'',
'header'=>'',
'body'=>'');
if(!empty($server)){
$errno = 0;
$errstr ='';
$this->mode = $mode;//debug or profile
$this->state = 'wait_start';
$this->sid = $sid;
$this->session_started = false;
$this->last_command = array();
$this->command = 'none';
$this->break_points = array();
$this->brkpts = array();
$this->received_brkpoints = array();
$this->data->set_data('calltip',false); //used for getting
variable values
$this->toolbar->setButtonsSensitive(array('stop','pause','con','in','over','out'),true);
if(!empty($sid)) $post =
'DBGSESSID='.$sid.'@clienthost:'.$dbg_port;
$url_sock =& $this->getSocket();
$sock =& $this->getSocket('listen.ip.0.0',$dbg_port,true);
if($url_sock && $sock){
$profile_window =
$this->notebook->createList('Profile',$this->list_headers,'profile');
$this->profile_window =&
$GLOBALS['message'][$profile_window]['list'];
$this->clearProfile();
if($this->mode == 'dbg'){
$this->toolbar->setButtonsSensitive(array('runweb','runcli'),false);
$bpt_window =
$this->notebook->createList('Breakpoints',$this->bpt_headers,'brkpt');
$this->bpt_window =&
$GLOBALS['message'][$bpt_window]['list'];
$this->clearBptWindow();
}
//print_r($this->profile_window);
$connected = @socket_connect($url_sock,$server,80);//lets start the
connection
if(!empty($post)){
$hdr = "POST /$page HTTP/1.0\r\n";
$hdr .= "Host: $server\r\n";
$hdr .= "Content-type:
application/x-www-form-urlencoded\r\n";
$hdr .= "Content-length:
".strlen($post)."\r\n";
$hdr .= "Accept: */*\r\n";
$hdr .= "\r\n";
$hdr .= "$post\r\n";
$tosend[$url_sock]['socket'] =& $url_sock;
$tosend[$url_sock]['data'] = $hdr;
$tosend[$url_sock]['state'] = 'header';
$tosend[$url_sock]['read'] =& $url_sock;
//echo "doing POST $post\n$hdr\n";
}else{
//echo "doing GET\n";
$hdr = "GET /$page HTTP/1.0\r\n";
$hdr .= "Host: $server\r\n";
$hdr .= "Accept: */*\r\n";
$hdr .= "\r\n";
$tosend[$url_sock]['socket'] =& $url_sock;
$tosend[$url_sock]['data'] = $hdr;
$tosend[$url_sock]['state'] = 'header';
$tosend[$url_sock]['read'] =& $url_sock;
}
$toget[$sock]['socket'] = $sock;
$r['state'] = 'connect';
//main loop
$GLOBALS['running'] = true;
while($r['state'] != 'done' &&
$GLOBALS['run'] === true){
while(gtk::events_pending()) gtk::main_iteration();
$num_changed_sockets = 0;
$write = null;
$read = null;
if(count($tosend) > 0){
foreach($tosend as $key=>$val){
$write[] =
$tosend[$key]['socket'];
}
}
if(count($toget) > 0){
foreach($toget as $key=>$val){
$read[] = $toget[$key]['socket'];
}
}
if(!is_array($read) && !is_array($write))
break;//$state = 'done';
if(is_array($read) || is_array($write)){
$num_changed_sockets = socket_select($read, $write,
$except = NULL, 0);
}
if($num_changed_sockets > 0 && is_array($write)){
foreach($write as $wsock){
if(isset($tosend[$wsock])){
$data =
$tosend[$wsock]['data'];
$len = strlen($data);
if($len > 0){
if(socket_write($wsock,
$data, $len)){
//echo "data
sent ok\n";
//print_r($tosend);
if(is_resource($tosend[$wsock]['read'])){
$toget[$wsock]['socket'] = $tosend[$wsock]['read'];
}
if(isset($tosend[$wsock]['state'])){
$r['state'] = $tosend[$wsock]['state'];
}
unset($tosend[$wsock]);
}else{
;//echo "send
returnrd false\n";
}
}
}
}
}
if($num_changed_sockets > 0 && is_array($read)){
foreach($read as $rsock){
if($rsock == $url_sock ){
switch($r['state']){
case 'disconnect':
break;
case 'header':
if(FALSE === ($str =
socket_read($rsock, 24))) {
//echo
"url_socket read() failed: reason: " . socket_strerror ($ret) . "\n";
$r['state'] = 'done';
}else{
$r['header'] .= $str;
if(!strlen($str) > 0){
//echo "page finished\n";
unset($toget[$rsock]);
//echo "\n\n".$r['header']."\n";
//$r['state'] = 'done';
}
$str =
'';
}
break;
}
}
if($rsock == $sock){
if (($msgsock =
@socket_accept($sock)) < 0) {
//echo "socket_accept()
failed: reason: " . socket_strerror ($msgsock) . "\n";
break;
}else{
//echo "unsetting
sock\n";
unset($toget[$sock]);
$toget[$msgsock]['socket'] =& $msgsock;
//$this->listen($msgsock,$tosend,$toget);
//unset($toget[$rsock]);
}
}
if($rsock == $msgsock){
$this->listen($msgsock,$tosend,$toget);
}
}
}
if($this->command !== 'none'){
$this->checkCommand($msgsock,$tosend,$toget);
}
}//while
$GLOBALS['running'] = false;
//echo "leaving main loop\n";
$this->toolbar->setButtonsSensitive(array('stop','pause','con','in','over','out'),false);
$this->toolbar->setButtonsSensitive(array('runweb','runcli'),true);
if($url_sock) socket_close($url_sock);
if($msgsock) socket_close($msgsock);
if($sock) socket_close($sock);
unset($url_sock);
unset($sock);
unset($msgsock);
}
}
return $r;
}
--
if its to much of a mess let me know and I'll trim it down to the
basics
regards,
Tom