<?PHP
/**
* patServer
* Classe base do servidor de soquete PHP
* Eventos que podem ser tratados:
* * onStart
* * onConnect
* * onConnectionRefused
* * onClose
* * no desligamento
* * onReceiveData
*
* @versão 1.1
* @autor Stephan Schmidt < [email protected] >
* @package patServer
*/
classe patServer{
/**
*informações sobre o projeto
* @var matriz $systemVars
*/
var $systemVars = array(
"appName" => "patServer",
"appVersion" => "1.1",
"autor" => array("Stephan Schmidt < [email protected] >", )
);
/**
* porta para ouvir
* @var inteiro $porta
*/
var $porta = 10000;
/**
* domínio ao qual vincular
* @var string $domínio
*/
var $domínio = "localhost";
/**
*quantidade máxima de clientes
* @var inteiro $maxClientes
*/
var $maxClientes = -1;
/**
* tamanho do buffer para socket_read
* @var inteiro $readBufferSize
*/
var $readBufferSize = 128;
/**
* caractere final para socket_read
* @var inteiro $readEndCharacter
*/
var $readEndCharacter = "n";
/**
* máximo de backlog na fila
* @var inteiro $maxQueue
*/
var $maxQueue = 500;
/**
* modo de depuração
* @var boolean $depurar
*/
var $depurar = verdadeiro;
/**
* modo de depuração
* @var string $debugMode
*/
var $debugMode = "texto";
/**
* destino de depuração (nome do arquivo ou stdout)
* @var string $debugDest
*/
var $debugDest = "stdout";
/**
* array vazio, usado para socket_select
* @var matriz $nulo
*/
var $nulo = array();
/**
* todos os descritores de arquivo são armazenados aqui
* @var matriz $clientFD
*/
var $clientFD = array();
/**
* necessário para armazenar informações do cliente
* @var array $clientInfo
*/
var $clientInfo = array();
/**
* necessário para armazenar informações do servidor
* @var matriz $serverInfo
*/
var $serverInfo = array();
/**
*quantidade de clientes
* @var inteiro $clientes
*/
var $clientes = 0;
/**
*crie um novo servidor de soquete
*
* @acessar público
* @param string $domain domínio ao qual vincular
* @param inteiro $port porta para escutar
*/
function patServer( $domain = "localhost", $port = 10000 ){
$este->domínio = $domínio;
$this->porta = $porta;
$this->serverInfo["domínio"] = $domínio;
$this->serverInfo["porta"] = $porta;
$this->serverInfo["nomeservidor"] = $this->systemVars["appName"];
$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];
set_time_limit(0);
}
/**
* definir quantidade máxima de conexões simultâneas
*
* @acessar público
* @param int $maxClientes
*/
function setMaxClients( $maxClients ){
$this->maxClients = $maxClients;
}
/**
* definir modo de depuração
*
* @acessar público
* @param misto $debug [text|htmlfalse]
* @param string $dest destino da mensagem de depuração (stdout para saída ou nome do arquivo se o log deve ser gravado)
*/
function setDebugMode($debug, $dest = "stdout" ){
if($depurar === falso){
$this->debug = false;
retornar verdadeiro;
}
$this->debug = true;
$this->debugMode = $debug;
$this->debugDest = $dest;
}
/**
*iniciar o servidor
*
* @acessar público
* @param int $maxClientes
*/
função início(){
$this->initFD = @socket_create(AF_INET, SOCK_STREAM, 0);
if(!$this->initFD)
die("patServer: Não foi possível criar o soquete.");
// endereço pode ser reutilizado
socket_setopt($this->initFD, SOL_SOCKET, SO_REUSEADDR, 1);
// vincula o soquete
if( !@socket_bind ( $this->initFD, $this->domain, $this->port ) ){
@socket_close($this->initFD);
die( "patServer: Não foi possível vincular o soquete a ".$this->domain." na porta ".$this->port." ( ".$this->getLastSocketError( $this->initFd )." )." );
}
// escuta na porta selecionada
if( !@socket_listen ( $this->initFD, $this->maxQueue ) )
die( "patServer: Não foi possível ouvir ( ".$this->getLastSocketError( $this->initFd ).." )." );
$this->sendDebugMessage( "Ouvindo na porta ".$this->port.". Servidor iniciado em ".date( "H:i:s", time() ) );
// isso permite que a função shutdown verifique se o servidor já está desligado
$GLOBALS["_patServerStatus"] = "em execução";
// isso garante que o servidor será encerrado corretamente
registrar_shutdown_function(array($this, "desligamento" ));
if(método_existe($this, "onStart" ) )
$this->onStart();
$this->serverInfo["iniciado"] = time();
$this->serverInfo["status"] = "em execução";
enquanto(verdadeiro){
$readFDs = array();
array_push($readFDs, $this->initFD);
//busca todos os clientes que estão aguardando conexões
for( $i = 0; $i <count( $this->clientFD ); $i++ )
if( isset( $this->clientFD[$i] ) )
array_push( $readFDs, $this->clientFD[$i] );
//bloqueia e espera por dados ou nova conexão
$ready = @socket_select( $readFDs, $this->null, $this->null, NULL );
if($pronto === falso){
$this->sendDebugMessage( "socket_select falhou." );
$this->desligamento();
}
// verifica se há nova conexão
if( in_array( $this->initFD, $readFDs ) ){
$newClient = $this->acceptConnection( $this->initFD );
// verifica a quantidade máxima de conexões
if( $this->maxClients > 0 ){
if( $this->clients > $this->maxClients ){
$this->sendDebugMessage( "Muitas conexões." );
if(método_existe($this, "onConnectionRefused") )
$this->onConnectionRefused( $newClient );
$this->closeConnection( $newClient );
}
}
if( --$pronto <= 0 )
continuar;
}
// verifica todos os clientes em busca de dados recebidos
for( $i = 0; $i <count( $this->clientFD ); $i++ ){
if( !isset( $this->clientFD[$i] ) )
continuar;
if( in_array( $this->clientFD[$i], $readFDs ) ){
$dados = $this->readFromSocket( $i );
// dados vazios => conexão foi fechada
if( !$dados ){
$this->sendDebugMessage( "Conexão fechada pelo peer");
$this->closeConnection( $i );
}outro{
$this->sendDebugMessage( "Recebido ".trim( $data )." de ".$i );
if(método_existe($this, "onReceiveData"))
$this->onReceiveData( $i, $data );
}
}
}
}
}
/**
* ler de um soquete
*
* @acessar privado
* @param inteiro $clientId ID interno do cliente para leitura
* @return string $dados dados que foram lidos
*/
função readFromSocket($clientId){
//começa com string vazia
$dados = "";
//lê dados do soquete
while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) ){
$dados .= $buf;
$endString = substr( $buf, - strlen( $this->readEndCharacter ) );
if($endString == $this->readEndCharacter)
quebrar;
if($buf == NULO)
quebrar;
}
if($buf === falso)
$this->sendDebugMessage( "Não foi possível ler do cliente ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." );
retornar $dados;
}
/**
* aceitar uma nova conexão
*
* @acessar público
* @param resource &$socket soquete que recebeu a nova conexão
* @return int $clientID ID interno do cliente
*/
função aceitarConnection( &$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,
"porta" => $peer_port,
"conectar" => tempo()
);
$this->clientes++;
$this->sendDebugMessage( "Nova conexão ( ".$i." ) de ".$peer_host." na porta ".$peer_port );
if(método_existe($this, "onConnect"))
$this->onConnect( $i );
retornar $i;
}
}
}
/**
* verifique se um cliente ainda está conectado
*
* @acessar público
* @param inteiro $id id do cliente
* @return boolean $connected true se o cliente estiver conectado, false caso contrário
*/
função éConectada( $id ){
if( !isset( $this->clientFD[$id] ) )
retornar falso;
retornar verdadeiro;
}
/**
* conexão próxima com um cliente
*
* @acessar público
* @param int $clientID ID interno do cliente
*/
function closeConnection( $id ){
if( !isset( $this->clientFD[$id] ) )
retornar falso;
if(método_existe($this, "onClose") )
$this->onClose( $id );
$this->sendDebugMessage( "Conexão fechada ( ".$id." ) de ".$this->clientInfo[$id]["host"]." na porta ".$this->clientInfo[$id][ "porta"]);
@socket_close($this->clientFD[$id]);
$this->clientFD[$id] = NULL;
unset( $this->clientInfo[$id] );
$this->clientes--;
}
/**
*desligar servidor
*
* @acessar público
*/
função desligar(){
if($GLOBALS["_patServerStatus"] != "em execução" )
saída;
$GLOBALS["_patServerStatus"] = "parado";
if(método_existe($this, "onShutdown" ) )
$this->onShutdown();
$maxFD = contagem( $this->clientFD );
for( $i = 0; $i < $maxFD; $i++ )
$this->closeConnection( $i );
@socket_close($this->initFD);
$this->sendDebugMessage( "Desligar servidor." );
saída;
}
/**
* obter a quantidade atual de clientes
*
* @acessar público
* @return int $clients quantidade de clientes
*/
função getClientes(){
return $this->clientes;
}
/**
* enviar dados para um cliente
*
* @acessar público
* @param int $clientId ID do cliente
* @param string $data dados a serem enviados
* @param boolean $debugData sinalizador para indicar se os dados gravados no soquete também devem ser enviados como mensagem de depuração
*/
function sendData($clientId, $data, $debugData = true){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
retornar falso;
if($debugData)
$this->sendDebugMessage( "enviando: "" . $data . "" para: $clientId" );
if( !@socket_write ( $this->clientFD[$clientId], $data ) )
$this->sendDebugMessage( "Não foi possível escrever '".$data."' cliente ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] )." )." ) ;
}
/**
* enviar dados para todos os clientes
*
* @acessar público
* @param string $data dados a serem enviados
* @param array $exclude IDs de clientes a serem excluídos
*/
função broadcastData($data, $exclude = array(), $debugData = true){
if( !empty( $excluir ) && !is_array( $excluir ) )
$excluir = array( $excluir );
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( "enviando: "" . $data . "" para: $i" );
if( !@socket_write ( $this->clientFD[$i], $data ) )
$this->sendDebugMessage( "Não foi possível escrever '".$data."' cliente ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] )." )." ) ;
}
}
}
/**
* obter informações atuais sobre um cliente
*
* @acessar público
* @param int $clientId ID do cliente
* @return array $info informações sobre o cliente
*/
função getClientInfo( $clientId ){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
retornar falso;
return $this->clientInfo[$clientId];
}
/**
* envie uma mensagem de depuração
*
* @acessar privado
* @param string $msg mensagem para depurar
*/
função sendDebugMessage( $msg ){
if(!$this->depurar)
retornar falso;
$msg = data( "Ymd H:i:s", hora() ) . " " . $mensagem;
switch( $this->debugMode ){
caso "texto":
$msg = $msg."n";
quebrar;
caso "html":
$msg = htmlspecialchars( $msg ). "<br />n";
quebrar;
}
if( $this->debugDest == "stdout" || vazio( $this->debugDest ) ){
eco $msg;
liberar();
retornar verdadeiro;
}
error_log($msg, 3, $this->debugDest);
retornar verdadeiro;
}
/**
* retorna string para o último erro de soquete
*
* @acessar público
* @return string $error último erro
*/
função getLastSocketError(&$fd){
$lastError = socket_last_error( $fd );
retorne "mensagem: ". socket_strerror($lastError). " / Código: ".$lastError;
}
função onReceiveData($ip,$dados){
$this->broadcastData( $data,array(), true );
}
}
$patServer = new patServer();
$patServer->start();
?>