<?PHP
/**
* patServer
* Clase base del servidor de socket PHP
* Eventos que se pueden manejar:
* * al iniciar
* * en Conectar
* * onConnectionRefused
* * al cerrar
* * al apagar
* * al recibir datos
*
* @versión 1.1
* @autor Stephan Schmidt < [email protected] >
* @paquetepatServer
*/
clase patServer{
/**
* información sobre el proyecto
* @var matriz $systemVars
*/
var $systemVars = array(
"appName" => "patServer",
"appVersion" => "1.1",
"autor" => matriz("Stephan Schmidt < [email protected] >", )
);
/**
* puerto para escuchar
* @var entero $puerto
*/
var $puerto = 10000;
/**
* dominio al que unirse
* @var cadena $dominio
*/
var $dominio = "localhost";
/**
* cantidad máxima de clientes
* @var entero $maxClients
*/
var $maxClientes = -1;
/**
* tamaño del buffer para socket_read
* @var entero $readBufferSize
*/
var $readBufferSize = 128;
/**
* carácter final para socket_read
* @var entero $readEndCharacter
*/
var $readEndCharacter = "n";
/**
* máximo de trabajo pendiente en cola
* @var entero $maxQueue
*/
var $maxQueue = 500;
/**
* modo de depuración
* @var booleano $depuración
*/
var $depuración = verdadero;
/**
* modo de depuración
* @var cadena $debugMode
*/
var $debugMode = "texto";
/**
* destino de depuración (nombre de archivo o salida estándar)
* @var cadena $debugDest
*/
var $debugDest = "salida estándar";
/**
* matriz vacía, utilizada para socket_select
* @var matriz $nulo
*/
var $nulo = matriz();
/**
* todos los descriptores de archivos se almacenan aquí
* @var matriz $clienteFD
*/
var $clienteFD = matriz();
/**
* necesario para almacenar información del cliente
* @var matriz $ clientInfo
*/
var $informacióncliente = matriz();
/**
* necesario para almacenar información del servidor
* @var matriz $serverInfo
*/
var $serverInfo = matriz();
/**
* cantidad de clientes
* @var entero $clientes
*/
var $clientes = 0;
/**
* crear un nuevo servidor de socket
*
* @acceso público
* @param cadena $dominio dominio al que vincularse
* @param entero $puerto puerto a escuchar
*/
función patServer( $dominio = "localhost", $puerto = 10000){
$este->dominio = $dominio;
$this->puerto = $puerto;
$this->serverInfo["dominio"] = $dominio;
$this->serverInfo["puerto"] = $puerto;
$this->serverInfo["servername"] = $this->systemVars["appName"];
$this->serverInfo["serverversion"] = $this->systemVars["appVersion"];
establecer_límite_tiempo(0);
}
/**
* establecer la cantidad máxima de conexiones simultáneas
*
* @acceso público
* @param int $maxClientes
*/
función setMaxClients( $maxClients ){
$this->maxClients = $maxClients;
}
/**
* establecer el modo de depuración
*
* @acceso público
* @param mixto $debug [texto|htmlfalse]
* @param cadena $dest destino del mensaje de depuración (salida estándar a la salida o nombre de archivo si se debe escribir el registro)
*/
función setDebugMode( $depuración, $dest = "salida estándar"){
si( $depuración === falso ){
$this->depurar = falso;
devolver verdadero;
}
$this->debug = verdadero;
$this->debugMode = $depurar;
$this->debugDest = $dest;
}
/**
* iniciar el servidor
*
* @acceso público
* @param int $maxClientes
*/
inicio de función(){
$this->initFD = @socket_create( AF_INET, SOCK_STREAM, 0 );
si( !$this->initFD )
die( "patServer: No se pudo crear el socket." );
// la dirección puede ser reutilizada
socket_setopt( $this->initFD, SOL_SOCKET, SO_REUSEADDR, 1 );
// vincular el socket
if( !@socket_bind ($this->initFD, $this->dominio, $this->puerto ) ){
@socket_close( $this->initFD );
die( "patServer: No se pudo vincular el socket a ".$this->domain." en el puerto ".$this->port." ( ".$this->getLastSocketError( $this->initFd ).". )." );
}
// escucha en el puerto seleccionado
if( !@socket_listen ($this->initFD, $this->maxQueue ) )
die( "patServer: No se pudo escuchar ( ".$this->getLastSocketError( $this->initFd ).". )." );
$this->sendDebugMessage( "Escuchando en el puerto ".$this->port.". El servidor se inició en ".date( "H:i:s", time() ) );
// esto permite que la función de apagado verifique si el servidor ya está apagado
$GLOBALS["_patServerStatus"] = "en ejecución";
// esto asegura que el servidor se apagará correctamente
Register_shutdown_function(matriz($this, "apagar"));
si (método_existe ($ esto, "onStart"))
$this->onStart();
$this->serverInfo["iniciado"] = tiempo();
$this->serverInfo["status"] = "en ejecución";
mientras (verdadero) {
$readFDs = matriz();
array_push( $readFDs, $this->initFD );
// busca todos los clientes que están esperando conexiones
para( $i = 0; $i < recuento( $this->clientFD ); $i++ )
if( isset( $this->clienteFD[$i] ) )
array_push( $readFDs, $this->clientFD[$i] );
// bloquear y esperar datos o nueva conexión
$listo = @socket_select( $readFDs, $this->null, $this->null, NULL);
si( $listo === falso ){
$this->sendDebugMessage( "falló socket_select." );
$this->apagar();
}
// busca nueva conexión
if( in_array( $this->initFD, $readFDs ) ){
$nuevoCliente = $this->acceptConnection( $this->initFD );
// comprueba la cantidad máxima de conexiones
si( $this->maxClients > 0 ){
if( $this->clientes > $this->maxClients ){
$this->sendDebugMessage( "Demasiadas conexiones." );
si (método_existe ($ esto, "onConnectionRefused"))
$this->onConnectionRefused( $nuevoCliente );
$this->closeConnection( $nuevoCliente );
}
}
si( --$listo <= 0 )
continuar;
}
// comprueba todos los clientes en busca de datos entrantes
para( $i = 0; $i < recuento( $este->clienteFD ); $i++ ){
if( !isset( $this->clienteFD[$i] ) )
continuar;
if( in_array( $this->clientFD[$i], $readFDs ) ){
$datos = $this->readFromSocket( $i );
// datos vacíos => la conexión se cerró
si( !$datos ){
$this->sendDebugMessage( "Conexión cerrada por par");
$this->closeConnection( $i );
}demás{
$this->sendDebugMessage( "Recibido ".trim( $data )." de ".$i );
si (método_existe ($ esto, "onReceiveData"))
$this->onReceiveData( $i, $datos );
}
}
}
}
}
/**
* leer desde un socket
*
* @acceso privado
* @param entero $clientId ID interno del cliente para leer
* @return cadena $datos datos que se leyeron
*/
función leerDesdeSocket($IdCliente){
//comienza con una cadena vacía
$datos = "";
// leer datos del socket
while( $buf = socket_read( $this->clientFD[$clientId], $this->readBufferSize ) ){
$datos .= $buf;
$endString = substr( $buf, - strlen( $this->readEndCharacter ) );
si( $endString == $this->readEndCharacter )
romper;
si ($buf == NULL)
romper;
}
si ($buf === falso)
$this->sendDebugMessage( "No se pudo leer del cliente ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] ).". )." );
devolver $datos;
}
/**
* aceptar una nueva conexión
*
* @acceso público
* @param recurso &$socket socket que recibió la nueva conexión
* @return int $clientID ID interno del cliente
*/
función aceptarConexión( &$socket ){
for( $i = 0 ; $i <= contar( $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] = matriz(
"host" => $peer_host,
"puerto" => $peer_port,
"conectar" => tiempo()
);
$this->clientes++;
$this->sendDebugMessage( "Nueva conexión ( ".$i." ) desde ".$peer_host." en el puerto ".$peer_port );
si (método_existe ($ esto, "onConnect"))
$this->onConnect( $i );
devolver $i;
}
}
}
/**
* comprobar si un cliente todavía está conectado
*
* @acceso público
* @param entero $id id del cliente
* @return booleano $connected verdadero si el cliente está conectado, falso en caso contrario
*/
la función está conectada ($id){
if( !isset( $this->clienteFD[$id] ) )
devolver falso;
devolver verdadero;
}
/**
* estrecha conexión con un cliente
*
* @acceso público
* @param int $clientID ID interno del cliente
*/
función cerrarConexión( $id ){
if( !isset( $this->clienteFD[$id] ) )
devolver falso;
si (método_existe ($ esto, "onClose"))
$this->onClose( $id );
$this->sendDebugMessage( "Conexión cerrada ( ".$id." ) desde ".$this->clientInfo[$id]["host"]." en el puerto ".$this->clientInfo[$id][ "puerto"] );
@socket_close( $this->clientFD[$id] );
$this->clientFD[$id] = NULL;
unset( $this->clientInfo[$id] );
$this->clientes--;
}
/**
* apagar el servidor
*
* @acceso público
*/
función apagar(){
if( $GLOBALS["_patServerStatus"] != "ejecutando" )
salida;
$GLOBALS["_patServerStatus"] = "detenido";
si (método_existe ($ esto, "onShutdown"))
$this->onShutdown();
$maxFD = recuento( $this->clientFD );
para( $i = 0; $i < $maxFD; $i++ )
$this->closeConnection( $i );
@socket_close( $this->initFD );
$this->sendDebugMessage( "Apagar servidor." );
salida;
}
/**
* obtener la cantidad actual de clientes
*
* @acceso público
* @return int $clients cantidad de clientes
*/
función obtenerClientes(){
devolver $this->clientes;
}
/**
* enviar datos a un cliente
*
* @acceso público
* @param int $clientId ID del cliente
* @param cadena $datos datos a enviar
* @param boolean $debugData indicador para indicar si los datos escritos en el socket también deben enviarse como mensaje de depuración
*/
función enviarDatos( $clientId, $datos, $debugData = verdadero ){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
devolver falso;
si ($ datos de depuración )
$this->sendDebugMessage( "enviando: "" . $datos . "" a: $clientId" );
if( !@socket_write ($this->clientFD[$clientId], $datos ) )
$this->sendDebugMessage( "No se pudo escribir '".$data."' cliente ".$clientId." ( ".$this->getLastSocketError( $this->clientFD[$clientId] ).". )." ) ;
}
/**
* enviar datos a todos los clientes
*
* @acceso público
* @param cadena $datos datos a enviar
* @param array $exclude ID de cliente a excluir
*/
función transmitir datos ($ datos, $ excluir = matriz (), $ datos de depuración = verdadero) {
if(! vacío( $excluir) &&!is_array( $excluir))
$excluir = matriz( $excluir );
para( $i = 0; $i < recuento( $este->clienteFD ); $i++ ){
if( isset( $this->clientFD[$i] ) && $this->clientFD[$i] != NULL && !in_array( $i, $excluir ) ){
si ($ datos de depuración )
$this->sendDebugMessage( "enviando: "" . $datos . "" a: $i" );
if( !@socket_write ($this->clientFD[$i], $datos ) )
$this->sendDebugMessage( "No se pudo escribir '".$data."' cliente ".$i." ( ".$this->getLastSocketError( $this->clientFD[$i] ).". )." ) ;
}
}
}
/**
* obtener información actualizada sobre un cliente
*
* @acceso público
* @param int $clientId ID del cliente
* @return array $info información sobre el cliente
*/
función getClientInfo( $clientId ){
if( !isset( $this->clientFD[$clientId] ) || $this->clientFD[$clientId] == NULL )
devolver falso;
devolver $this->clientInfo[$clientId];
}
/**
* enviar un mensaje de depuración
*
* @acceso privado
* @param string $msg mensaje para depurar
*/
función enviarDebugMessage( $msg ){
si( !$this->depurar )
devolver falso;
$msg = fecha( "Ymd H:i:s", hora() ). " " . $mensaje;
cambiar( $this->debugMode ){
caso "texto":
$mensaje = $mensaje."n";
romper;
caso "html":
$msg = htmlspecialchars( $msg). "<br />n";
romper;
}
if( $this->debugDest == "stdout" || vacío( $this->debugDest ) ){
eco $mensaje;
enjuagar();
devolver verdadero;
}
error_log( $msg, 3, $this->debugDest );
devolver verdadero;
}
/**
* cadena de retorno para el último error de socket
*
* @acceso público
* @return string $error último error
*/
función getLastSocketError( &$fd ){
$últimoError = socket_last_error( $fd );
devolver "mensaje: ". socket_strerror($últimoError). " / Código: ".$lastError;
}
función onReceiveData($ip,$datos){
$this->broadcastData( $datos,matriz(), verdadero);
}
}
$patServer = nuevo patServer();
$patServer->start();
?>