php socket实现的聊天室代码分享
程序员文章站
2023-10-30 14:39:34
/**
* patserver
* php socket server base class
* events that can be handled:
*...
/** * patserver * php socket server base class * events that can be handled: * * onstart * * onconnect * * onconnectionrefused * * onclose * * onshutdown * * onreceivedata * * @version 1.1 * @author stephan schmidt <schst@php-tools.de> * @package patserver */ class patserver { /** * information about the project * @var array $systemvars */ var $systemvars = array( "appname" => "patserver", "appversion" => "1.1", "author" => array("stephan schmidt <schst@php-tools.de>", ) ); /** * port to listen * @var integer $port */ var $port = 10000; /** * domain to bind to * @var string $domain */ var $domain = "localhost"; /** * maximum amount of clients * @var integer $maxclients */ var $maxclients = -1; /** * buffer size for socket_read * @var integer $readbuffersize */ var $readbuffersize = 128; /** * end character for socket_read * @var integer $readendcharacter */ var $readendcharacter = "\n"; /** * maximum of backlog in queue * @var integer $maxqueue */ var $maxqueue = 500; /** * debug mode * @var boolean $debug */ var $debug = true; /** * debug mode * @var string $debugmode */ var $debugmode = "text"; /** * debug destination (filename or stdout) * @var string $debugdest */ var $debugdest = "stdout"; /** * empty array, used for socket_select * @var array $null */ var $null = array(); /** * all file descriptors are stored here * @var array $clientfd */ var $clientfd = array(); /** * needed to store client information * @var array $clientinfo */ var $clientinfo = array(); /** * needed to store server information * @var array $serverinfo */ var $serverinfo = array(); /** * amount of clients * @var integer $clients */ var $clients = 0; /** * create a new socket server * * @access public * @param string $domain domain to bind to * @param integer $port port to listen to */ function patserver( $domain = "localhost", $port = 10000 ) { $this->domain = $domain; $this->port = $port; $this->serverinfo["domain"] = $domain; $this->serverinfo["port"] = $port; $this->serverinfo["servername"] = $this->systemvars["appname"]; $this->serverinfo["serverversion"] = $this->systemvars["appversion"]; set_time_limit( 0 ); } /** * set maximum amount of simultaneous connections * * @access public * @param int $maxclients */ function setmaxclients( $maxclients ) { $this->maxclients = $maxclients; } /** * set debug mode * * @access public * @param mixed $debug [text|htmlfalse] * @param string $dest destination of debug message (stdout to output or filename if log should be written) */ function setdebugmode( $debug, $dest = "stdout" ) { if( $debug === false ) { $this->debug = false; return true; } $this->debug = true; $this->debugmode = $debug; $this->debugdest = $dest; } /** * start the server * * @access public * @param int $maxclients */ function start() { $this->initfd = @socket_create( af_inet, sock_stream, 0 ); if( !$this->initfd ) die( "patserver: could not create socket." ); // adress may be reused socket_setopt( $this->initfd, sol_socket, so_reuseaddr, 1 ); // bind the socket if( !@socket_bind( $this->initfd, $this->domain, $this->port ) ) { @socket_close( $this->initfd ); die( "patserver: could not bind socket to ".$this->domain." on port ".$this->port." ( ".$this->getlastsocketerror( $this->initfd )." )." ); } // listen on selected port if( !@socket_listen( $this->initfd, $this->maxqueue ) ) die( "patserver: could not listen ( ".$this->getlastsocketerror( $this->initfd )." )." ); $this->senddebugmessage( "listening on port ".$this->port.". server started at ".date( "h:i:s", time() ) ); // this allows the shutdown function to check whether the server is already shut down $globals["_patserverstatus"] = "running"; // this ensures that the server will be sutdown correctly register_shutdown_function( array( $this, "shutdown" ) ); if( method_exists( $this, "onstart" ) ) $this->onstart(); $this->serverinfo["started"] = time(); $this->serverinfo["status"] = "running"; while( true ) { $readfds = array(); array_push( $readfds, $this->initfd ); // fetch all clients that are awaiting connections for( $i = 0; $i < count( $this->clientfd ); $i++ ) if( isset( $this->clientfd[$i] ) ) array_push( $readfds, $this->clientfd[$i] ); // block and wait for data or new connection $ready = @socket_select( $readfds, $this->null, $this->null, null ); if( $ready === false ) { $this->senddebugmessage( "socket_select failed." ); $this->shutdown(); } // check for new connection if( in_array( $this->initfd, $readfds ) ) { $newclient = $this->acceptconnection( $this->initfd ); // check for maximum amount of connections if( $this->maxclients > 0 ) { if( $this->clients > $this->maxclients ) { $this->senddebugmessage( "too many connections." ); if( method_exists( $this, "onconnectionrefused" ) ) $this->onconnectionrefused( $newclient ); $this->closeconnection( $newclient ); } } if( --$ready <= 0 ) continue; } // check all clients for incoming data for( $i = 0; $i < count( $this->clientfd ); $i++ ) { if( !isset( $this->clientfd[$i] ) ) continue; if( in_array( $this->clientfd[$i], $readfds ) ) { $data = $this->readfromsocket( $i ); // empty data => connection was closed if( !$data ) { $this->senddebugmessage( "connection closed by peer" ); $this->closeconnection( $i ); } else { $this->senddebugmessage( "received ".trim( $data )." from ".$i ); if( method_exists( $this, "onreceivedata" ) ) $this->onreceivedata( $i, $data ); } } } } } /** * read from a socket * * @access private * @param integer $clientid internal id of the client to read from * @return string $data data that was read */ function readfromsocket( $clientid ) { // start with empty string $data = ""; // read data from socket while( $buf = socket_read( $this->clientfd[$clientid], $this->readbuffersize ) ) { $data .= $buf; $endstring = substr( $buf, - strlen( $this->readendcharacter ) ); if( $endstring == $this->readendcharacter ) break; if( $buf == null ) break; } if( $buf === false ) $this->senddebugmessage( "could not read from client ".$clientid." ( ".$this->getlastsocketerror( $this->clientfd[$clientid] )." )." ); return $data; } /** * accept a new connection * * @access public * @param resource &$socket socket that received the new connection * @return int $clientid internal id of the client */ function acceptconnection( &$socket ) { for( $i = 0 ; $i <= count( $this->clientfd ); $i++ ) { if( !isset( $this->clientfd[$i] ) || $this->clientfd[$i] == null ) { $this->clientfd[$i] = socket_accept( $socket ); socket_setopt( $this->clientfd[$i], sol_socket, so_reuseaddr, 1 ); $peer_host = ""; $peer_port = ""; socket_getpeername( $this->clientfd[$i], $peer_host, $peer_port ); $this->clientinfo[$i] = array( "host" => $peer_host, "port" => $peer_port, "connecton" => time() ); $this->clients++; $this->senddebugmessage( "new connection ( ".$i." ) from ".$peer_host." on port ".$peer_port ); if( method_exists( $this, "onconnect" ) ) $this->onconnect( $i ); return $i; } } } /** * check, whether a client is still connected * * @access public * @param integer $id client id * @return boolean $connected true if client is connected, false otherwise */ function isconnected( $id ) { if( !isset( $this->clientfd[$id] ) ) return false; return true; } /** * close connection to a client * * @access public * @param int $clientid internal id of the client */ function closeconnection( $id ) { if( !isset( $this->clientfd[$id] ) ) return false; if( method_exists( $this, "onclose" ) ) $this->onclose( $id ); $this->senddebugmessage( "closed connection ( ".$id." ) from ".$this->clientinfo[$id]["host"]." on port ".$this->clientinfo[$id]["port"] ); @socket_close( $this->clientfd[$id] ); $this->clientfd[$id] = null; unset( $this->clientinfo[$id] ); $this->clients--; } /** * shutdown server * * @access public */ function shutdown() { if( $globals["_patserverstatus"] != "running" ) exit; $globals["_patserverstatus"] = "stopped"; if( method_exists( $this, "onshutdown" ) ) $this->onshutdown(); $maxfd = count( $this->clientfd ); for( $i = 0; $i < $maxfd; $i++ ) $this->closeconnection( $i ); @socket_close( $this->initfd ); $this->senddebugmessage( "shutdown server." ); exit; } /** * get current amount of clients * * @access public * @return int $clients amount of clients */ function getclients() { return $this->clients; } /** * send data to a client * * @access public * @param int $clientid id of the client * @param string $data data to send * @param boolean $debugdata flag to indicate whether data that is written to socket should also be sent as debug message */ function senddata( $clientid, $data, $debugdata = true ) { if( !isset( $this->clientfd[$clientid] ) || $this->clientfd[$clientid] == null ) return false; if( $debugdata ) $this->senddebugmessage( "sending: \"" . $data . "\" to: $clientid" ); if( !@socket_write( $this->clientfd[$clientid], $data ) ) $this->senddebugmessage( "could not write '".$data."' client ".$clientid." ( ".$this->getlastsocketerror( $this->clientfd[$clientid] )." )." ); } /** * send data to all clients * * @access public * @param string $data data to send * @param array $exclude client ids to exclude */ function broadcastdata( $data, $exclude = array(), $debugdata = true ) { if( !empty( $exclude ) && !is_array( $exclude ) ) $exclude = array( $exclude ); for( $i = 0; $i < count( $this->clientfd ); $i++ ) { if( isset( $this->clientfd[$i] ) && $this->clientfd[$i] != null && !in_array( $i, $exclude ) ) { if( $debugdata ) $this->senddebugmessage( "sending: \"" . $data . "\" to: $i" ); if( !@socket_write( $this->clientfd[$i], $data ) ) $this->senddebugmessage( "could not write '".$data."' client ".$i." ( ".$this->getlastsocketerror( $this->clientfd[$i] )." )." ); } } } /** * get current information about a client * * @access public * @param int $clientid id of the client * @return array $info information about the client */ function getclientinfo( $clientid ) { if( !isset( $this->clientfd[$clientid] ) || $this->clientfd[$clientid] == null ) return false; return $this->clientinfo[$clientid]; } /** * send a debug message * * @access private * @param string $msg message to debug */ function senddebugmessage( $msg ) { if( !$this->debug ) return false; $msg = date( "y-m-d h:i:s", time() ) . " " . $msg; switch( $this->debugmode ) { case "text": $msg = $msg."\n"; break; case "html": $msg = htmlspecialchars( $msg ) . "<br />\n"; break; } if( $this->debugdest == "stdout" || empty( $this->debugdest ) ) { echo $msg; flush(); return true; } error_log( $msg, 3, $this->debugdest ); return true; } /** * return string for last socket error * * @access public * @return string $error last error */ function getlastsocketerror( &$fd ) { $lasterror = socket_last_error( $fd ); return "msg: " . socket_strerror( $lasterror ) . " / code: ".$lasterror; } function onreceivedata($ip,$data){ $this->broadcastdata( $data,array(), true ); } } $patserver = new patserver(); $patserver->start();