Re: HELP! Socket server w/multiple connections

From: 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

« previous php.general (#184704) next »