{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 functiongetPidFile()
{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 functionstart()
{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() );
}
}?>