<?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() );
}
}
?>
This php script works like a socket service daemon. It can handle multiple calls.
1 Response
Write a comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.