/**
*patServer
*PHPsocketserverbaseclass
*Eventsthatcanbehandled:
**onStart
**onConnect
**onConnectionRefused
**onClose
**onShutdown
**onReceiveData
*
*@version1.1
*@authorStephanSchmidt<schst@php-tools.de>
*@packagepatServer
*/
classpatServer{
/**
*informationabouttheproject
*@vararray$systemVars
*/
var$systemVars=array(
"appName"=>"patServer",
"appVersion"=>"1.1",
"author"=>array("StephanSchmidt<schst@php-tools.de>",)
);
/**
*porttolisten
*@varinteger$port
*/
var$port=10000;
/**
*domaintobindto
*@varstring$domain
*/
var$domain="localhost";
/**
*maximumamountofclients
*@varinteger$maxClients
*/
var$maxClients=-1;
/**
*buffersizeforsocket_read
*@varinteger$readBufferSize
*/
var$readBufferSize=128;
/**
*endcharacterforsocket_read
*@varinteger$readEndCharacter
*/
var$readEndCharacter="\n";
/**
*maximumofbackloginqueue
*@varinteger$maxQueue
*/
var$maxQueue=500;
/**
*debugmode
*@varboolean$debug
*/
var$debug=true;
/**
*debugmode
*@varstring$debugMode
*/
var$debugMode="text";
/**
*debugdestination(filenameorstdout)
*@varstring$debugDest
*/
var$debugDest="stdout";
/**
*emptyarray,usedforsocket_select
*@vararray$null
*/
var$null=array();
/**
*allfiledescriptorsarestoredhere
*@vararray$clientFD
*/
var$clientFD=array();
/**
*neededtostoreclientinformation
*@vararray$clientInfo
*/
var$clientInfo=array();
/**
*neededtostoreserverinformation
*@vararray$serverInfo
*/
var$serverInfo=array();
/**
*amountofclients
*@varinteger$clients
*/
var$clients=0;
/**
*createanewsocketserver
*
*@accesspublic
*@paramstring$domaindomaintobindto
*@paraminteger$portporttolistento
*/
functionpatServer($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);
}
/**
*setmaximumamountofsimultaneousconnections
*
*@accesspublic
*@paramint$maxClients
*/
functionsetMaxClients($maxClients)
{
$this->maxClients=$maxClients;
}
/**
*setdebugmode
*
*@accesspublic
*@parammixed$debug[text|htmlfalse]
*@paramstring$destdestinationofdebugmessage(stdouttooutputorfilenameiflogshouldbewritten)
*/
functionsetDebugMode($debug,$dest="stdout")
{
if($debug===false)
{
$this->debug=false;
returntrue;
}
$this->debug=true;
$this->debugMode=$debug;
$this->debugDest=$dest;
}
/**
*starttheserver
*
*@accesspublic
*@paramint$maxClients
*/
functionstart()
{
$this->initFD=@socket_create(AF_INET,SOCK_STREAM,0);
if(!$this->initFD)
die("patServer:Couldnotcreatesocket.");
//adressmaybereused
socket_setopt($this->initFD,SOL_SOCKET,SO_REUSEADDR,1);
//bindthesocket
if(!@socket_bind($this->initFD,$this->domain,$this->port))
{
@socket_close($this->initFD);
die("patServer:Couldnotbindsocketto".$this->domain."onport".$this->port."(".$this->getLastSocketError($this->initFd).").");
}
//listenonselectedport
if(!@socket_listen($this->initFD,$this->maxQueue))
die("patServer:Couldnotlisten(".$this->getLastSocketError($this->initFd).").");
$this->sendDebugMessage("Listeningonport".$this->port.".Serverstartedat".date("H:i:s",time()));
//thisallowstheshutdownfunctiontocheckwhethertheserverisalreadyshutdown
$GLOBALS["_patServerStatus"]="running";
//thisensuresthattheserverwillbesutdowncorrectly
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);
//fetchallclientsthatareawaitingconnections
for($i=0;$i<count($this->clientFD);$i++)
if(isset($this->clientFD[$i]))
array_push($readFDs,$this->clientFD[$i]);
//blockandwaitfordataornewconnection
$ready=@socket_select($readFDs,$this->null,$this->null,NULL);
if($ready===false)
{
$this->sendDebugMessage("socket_selectfailed.");
$this->shutdown();
}
//checkfornewconnection
if(in_array($this->initFD,$readFDs))
{
$newClient=$this->acceptConnection($this->initFD);
//checkformaximumamountofconnections
if($this->maxClients>0)
{
if($this->clients>$this->maxClients)
{
$this->sendDebugMessage("Toomanyconnections.");
if(method_exists($this,"onConnectionRefused"))
$this->onConnectionRefused($newClient);
$this->closeConnection($newClient);
}
}
if(--$ready<=0)
continue;
}
//checkallclientsforincomingdata
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);
//emptydata=>connectionwasclosed
if(!$data)
{
$this->sendDebugMessage("Connectionclosedbypeer");
$this->closeConnection($i);
}
else
{
$this->sendDebugMessage("Received".trim($data)."from".$i);
if(method_exists($this,"onReceiveData"))
$this->onReceiveData($i,$data);
}
}
}
}
}
/**
*readfromasocket
*
*@accessprivate
*@paraminteger$clientIdinternalidoftheclienttoreadfrom
*@returnstring$datadatathatwasread
*/
functionreadFromSocket($clientId)
{
//startwithemptystring
$data="";
//readdatafromsocket
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("Couldnotreadfromclient".$clientId."(".$this->getLastSocketError($this->clientFD[$clientId]).").");
return$data;
}
/**
*acceptanewconnection
*
*@accesspublic
*@paramresource&$socketsocketthatreceivedthenewconnection
*@returnint$clientIDinternalIDoftheclient
*/
functionacceptConnection(&$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("Newconnection(".$i.")from".$peer_host."onport".$peer_port);
if(method_exists($this,"onConnect"))
$this->onConnect($i);
return$i;
}
}
}
/**
*check,whetheraclientisstillconnected
*
*@accesspublic
*@paraminteger$idclientid
*@returnboolean$connectedtrueifclientisconnected,falseotherwise
*/
functionisConnected($id)
{
if(!isset($this->clientFD[$id]))
returnfalse;
returntrue;
}
/**
*closeconnectiontoaclient
*
*@accesspublic
*@paramint$clientIDinternalIDoftheclient
*/
functioncloseConnection($id)
{
if(!isset($this->clientFD[$id]))
returnfalse;
if(method_exists($this,"onClose"))
$this->onClose($id);
$this->sendDebugMessage("Closedconnection(".$id.")from".$this->clientInfo[$id]["host"]."onport".$this->clientInfo[$id]["port"]);
@socket_close($this->clientFD[$id]);
$this->clientFD[$id]=NULL;
unset($this->clientInfo[$id]);
$this->clients--;
}
/**
*shutdownserver
*
*@accesspublic
*/
functionshutDown()
{
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("Shutdownserver.");
exit;
}
/**
*getcurrentamountofclients
*
*@accesspublic
*@returnint$clientsamountofclients
*/
functiongetClients()
{
return$this->clients;
}
/**
*senddatatoaclient
*
*@accesspublic
*@paramint$clientIdIDoftheclient
*@paramstring$datadatatosend
*@paramboolean$debugDataflagtoindicatewhetherdatathatiswrittentosocketshouldalsobesentasdebugmessage
*/
functionsendData($clientId,$data,$debugData=true)
{
if(!isset($this->clientFD[$clientId])||$this->clientFD[$clientId]==NULL)
returnfalse;
if($debugData)
$this->sendDebugMessage("sending:\"".$data."\"to:$clientId");
if(!@socket_write($this->clientFD[$clientId],$data))
$this->sendDebugMessage("Couldnotwrite'".$data."'client".$clientId."(".$this->getLastSocketError($this->clientFD[$clientId]).").");
}
/**
*senddatatoallclients
*
*@accesspublic
*@paramstring$datadatatosend
*@paramarray$excludeclientidstoexclude
*/
functionbroadcastData($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("Couldnotwrite'".$data."'client".$i."(".$this->getLastSocketError($this->clientFD[$i]).").");
}
}
}
/**
*getcurrentinformationaboutaclient
*
*@accesspublic
*@paramint$clientIdIDoftheclient
*@returnarray$infoinformationabouttheclient
*/
functiongetClientInfo($clientId)
{
if(!isset($this->clientFD[$clientId])||$this->clientFD[$clientId]==NULL)
returnfalse;
return$this->clientInfo[$clientId];
}
/**
*sendadebugmessage
*
*@accessprivate
*@paramstring$msgmessagetodebug
*/
functionsendDebugMessage($msg)
{
if(!$this->debug)
returnfalse;
$msg=date("Y-m-dH: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();
returntrue;
}
error_log($msg,3,$this->debugDest);
returntrue;
}
/**
*returnstringforlastsocketerror
*
*@accesspublic
*@returnstring$errorlasterror
*/
functiongetLastSocketError(&$fd)
{
$lastError=socket_last_error($fd);
return"msg:".socket_strerror($lastError)."/Code:".$lastError;
}
functiononReceiveData($ip,$data){
$this->broadcastData($data,array(),true);
}
}
$patServer=newpatServer();
$patServer->start();