<?php class PHPSocketServer { const ERROR = 0; const LOG = 1; const DEBUG = 2; private $aConfig = NULL; private $hSocket = NULL; private $aClients = array(); private $iRunning = 0; private $iStartTime = 0; private $iLastActivity = 0; private static $aDefaults = array ( 'main' => array ( 'address' => 'localhost', 'port' => 15151, 'backlog' => 200, 'select_timeout' => 1, 'pid_file' => 'phpserver.pid' ), 'verbosity' => array ( 'echo_log' => 1, 'echo_debug' => 0, 'echo_errors' => 1, 'log_errors' => 1 ) ); public function getPidFile() { return $this->aConfig['main']['pid_file']; } private static function getName( $hSocket ) { return socket_getpeername( $hSocket, $sAddress, $iPort ) ? "$sAddress:$iPort" : "?????:???"; } private function log( $sMessage, $iType ) { $sTimeStamp = @strftime( '%c' ); if( $iType == self::ERROR ) { $aBacktrace = debug_backtrace(); $aBacktrace = $aBacktrace[1]; $sMessage = sprintf( '[%s] [E] %s%s%s [%d] : %s', $sTimeStamp, $aBacktrace['class'], $aBacktrace['type'], $aBacktrace['function'], $aBacktrace['line'], $sMessage ); if( $this->aConfig['verbosity']['echo_errors'] ) printf( "$sMessage\n" ); if( $this->aConfig['verbosity']['log_errors'] ) error_log( $sMessage ); } else if( $iType == self::DEBUG && $this->aConfig['verbosity']['echo_debug'] ) { echo sprintf( '[%s] [D] : %s', $sTimeStamp, $sMessage )."\n"; } else if( $iType == self::LOG && $this->aConfig['verbosity']['echo_log'] ) { echo sprintf( '[%s] [L] : %s', $sTimeStamp, $sMessage )."\n"; } } /* * Handle clients here. */ private function handleClientRequest( $hClient, $iClientIndex ) { /* * ... */ $this->disconnect( $iClientIndex ); } private function disconnect( $i ) { socket_close( $this->aClients[ $i ] ); $this->aClients[ $i ] = NULL; } private function loadConfiguration( $sConfigFile ) { if( !is_file( $sConfigFile ) || !is_readable( $sConfigFile ) ) die( "Could not read $sConfigFile.\n" ); else if( !( $this->aConfig = parse_ini_file( $sConfigFile, TRUE ) ) ) die( "Could not parse $sConfigFile.\n" ); foreach( self::$aDefaults as $sSection => $aDefaultValues ) { if( !isset( $this->aConfig[ $sSection ] ) ) $this->aConfig[ $sSection ] = array(); foreach( $aDefaultValues as $sName => $sValue ) { if( !isset( $this->aConfig[ $sSection ][ $sName ] ) ) $this->aConfig[ $sSection ][ $sName ] = $sValue; } } } public function setConfig( $sSectionName, $sConfigName, $mValue ) { if( !isset( $this->aConfig[ $sSectionName ] ) ) $this->aConfig[ $sSectionName ] = array(); $this->aConfig[ $sSectionName ][ $sConfigName ] = $mValue; } public function __construct( $sConfigFile ) { $this->loadConfiguration( $sConfigFile ); if( !( $this->hSocket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ) ) ) $this->log( 'Could not create main socket ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); else if( socket_set_option( $this->hSocket, SOL_SOCKET, SO_REUSEADDR, 1 ) === FALSE ) $this->log( 'Could not set SO_REUSEADDR flag ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); } public function start() { if( !socket_bind( $this->hSocket, $this->aConfig['main']['address'], $this->aConfig['main']['port'] ) ) $this->log( 'Could not bind on '.$this->aConfig['main']['address'].':'.$this->aConfig['main']['port'].' ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); else if( !socket_listen( $this->hSocket, $this->aConfig['main']['backlog'] ) ) $this->log( 'Could not put main socket in listening mode ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); else if( !socket_set_nonblock( $this->hSocket ) ) $this->log( 'Could not set main socket in non-blocking mode ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); else { $this->iStartTime = time(); $this->log( 'Server started on '.$this->aConfig['main']['address'].':'.$this->aConfig['main']['port'].' .', self::LOG ); for(;;) { $aRead = array_merge( array( $this->hSocket ), $this->aClients ); if( socket_select( $aRead, $aWrite, $aExcept, $this->aConfig['main']['select_timeout'] ) ) { if( in_array( $this->hSocket, $aRead ) ) { if( ( $hClient = @socket_accept( $this->hSocket ) ) ) { $this->aClients[ microtime(TRUE) ] = $hClient; $this->iLastActivity = time(); $this->log( 'New incoming connection '.self::getName( $hClient ), self::DEBUG ); } else $this->log( 'Could not accept a new connection ( '.socket_strerror( socket_last_error() ).' ).', self::ERROR ); } } /* * Search for readable clients. */ foreach( $this->aClients as $i => $hClient ) { if( in_array( $hClient, $aRead ) ) { $this->handleClientRequest( $hClient, $i ); } } /* * Remove closed connections. */ $this->aClients = array_filter( $this->aClients ); } } } public function __destruct() { if( $this->hSocket ) { $this->log( 'Shutting down ...', self::LOG ); foreach( $this->aClients as $sId => $hClient ) { if( $hClient ) socket_close( $hClient ); } socket_close( $this->hSocket ); } @unlink( $this->getPidFile() ); } } ?>