Underc0de

Programación Web => Back-end => Mensaje iniciado por: Alex en Octubre 07, 2013, 05:50:40 PM

Título: CHAT WebSocket y php, con PHPCLI y mis disculpas jejeje
Publicado por: Alex en Octubre 07, 2013, 05:50:40 PM
Buenas, ayer un amigo me comentó su necesidad de hacer un chat con Websockets (ya había intentado hacer un chat con AJAX y es el desmadre de consumo jajaja, aunque me lo imaginaba tenía que verlo con mis propios hojos), asique busqué un ejemplo y estube varias horas tratando de entender por que no andaba, hasta que me di cuenta que el protocolo había cambiado y ahora se usaba una clave de encriptación basada en XOR con dígitos de control. Me puse a trabajar y mientras buscaba algunas ayudas en internet me encontré con un repositorio y lo que yo llamo "la clase perfecta" si bien le falta pulir mucho para que sea la perfecta clase que mensiono, era la solución para no romperme más la cabeza, y para probar su funcionamiento en unas horas hice un sistema de chat basado en esa clase y ajustando un par de cosas.

Clase Original aquí (https://github.com/ghedipunk/PHP-Websockets/blob/master/websockets.php)

ahora mis disculpas ¿por que? por que el código que voy a poner a continuación es un azco, realmente un desorden, por lo que en mi defensa diré simplemente que quería probar la clase, y no reparé en la estructura del código, y en criterio de calidad alguno. De hecho si se fijan no eh tenido tiempo ni de implementar las interfaces, igual primero debería revisar la estructura de la clase.
Probablemente en unos días les presentaré la versión refinada (como hice en el código de bbc)

Ahora que ya saben que se tienen que esperar un azco de código mezclado y sin estructura aparente, les dejo mi desastroso pero funcional código.

Código (php) [Seleccionar]
<?php

/** WebSocketServer
 *  @original code: https://github.com/ghedipunk/PHP-Websockets/blob/master/users.php
 *  @redesign: Alexander171294
 *  @Proyect Name: PHSP: phSocketPlus
 *  @contact: [email protected]
 *  @Status: Prototype
 *  @Date: 07/10/13  
 */           
 
interface iphSocketPlus
{

}

interface 
iSocketPlusUser
{

}
 
trait 
Property // mi hermosa clase property
{
    
// llamando a funciones setters
    
Public function __set($property$value)
    {
            return 
call_user_func(array($this'set_'.$property), $value); 
    }
    
    
// llamando a funciones getters
    
Public function __get($property)
    {
        if(
is_callable(array($this'get_'.$property))) 
            return 
call_user_func(array($this'get_'.$property)); 
        elseif(
is_callable(array(parent'get_'.$property))) 
            return 
call_user_func(array(parent'get_'.$property)); 
        else 
        
// no hay funcion getter para este atributo (o no existe el atributo)
            
throw new exception('The atribute $'.$property.' not exist in get');  
    }
}

abstract class 
AbstractWebSocketUser {
  private 
$socket;
  private 
$id;
  private 
$headers = array();
  private 
$handshake false;

  private 
$handlingPartialPacket false;
  private 
$partialBuffer "";

  private 
$sendingContinuous false;
  private 
$partialMessage "";

  private 
$hasSentClose false;
  
  private 
$RequestedResource null;

  public function 
__construct($id,$socket
  {
    
$this->id $id;
    
$this->socket $socket;
  }
  
  public function 
get_socket()
  {
    return 
$this->socket;
  }
  
  public function 
get_id()
  {
    return 
$this->id;
  }
  
  public function 
get_headers()
  {
    return 
$this->headers;
  }
  
  public function 
get_handshake()
  {
    return 
$this->handshake;
  }
  
  public function 
get_handlingPartialPacket()
  {
    return 
$this->handlingPartialPacket;
  }
  
  public function 
get_partialBuffer()
  {
    return 
$this->partialBuffer;
  }
  
  public function 
get_sendingContinuous()
  {
    return 
$this->sendingContinuous;
  }
  
  public function 
get_partialMessage()
  {
    return 
$this->partialMessage;
  }
  
  public function 
get_hasSentClose()
  {
    return 
$this->hasSentClose;
  }
  
  public function 
get_RequestedResource()
  {
    return 
$this->RequestedResource;
  }
  
  public function 
set_RequestedResource($value)
  {
    
$this->RequestedResource $value;
  }
  
  public function 
set_socket($value)
  {
    
$this->socket $value;
  }
  
  public function 
set_id($value)
  {
    
$this->id $value;
  }
  
  public function 
set_headers($value)
  {
    
$this->headers $value;
  }
  
  public function 
set_handshake($value)
  {
    
$this->handshake $value;
  }
  
  public function 
set_handlingPartialPacket($value)
  {
    
$this->handlingPartialPacket $value;
  }
  
  public function 
set_partialBuffer($value)
  {
    
$this->partialBuffer $value;
  }
  
  public function 
set_sendingContinuous($value)
  {
    
$this->sendingContinuous $value;
  }
  
  public function 
set_partialMessage($value)
  {
    
$this->partialMessage $value;
  }
  
  public function 
set_hasSentClose($value)
  {
    
$this->hasSentClose $value;
  }
}

class 
SocketPlusUser extends AbstractWebSocketUser implements iSocketPlusUser
{
use 
Property;

    private 
$accedio false// si accedió
    
private $name null// nombre de usuario
    
private $channel null;
    private 
$founder false;
    private 
$moderator false;
    static 
$users = array();
    static 
$channels = array(); // array complejo
    
static $uonlines = array();
    
    
// acceder
    
public function acceder($user$pass)
    {
        if(isset(
self::$users[$user]) && self::$users[$user]==$pass)
        {
            
$this->accedio true;
            
$this->name $user;
            return 
true;
        } else {return 
false;}
    }
    
    public function 
registrarse($user$pass)
    {
        if(
strlen($user)<|| strlen($pass)<3) return false;
        if(!isset(
self::$users[$user]))
        {
            
self::$users[$user] = $pass;
            return 
true;
        } else {return 
false;}
    }
    
    public function 
crear_canal($name$desc)
    {
        if(
strlen($name)<|| strlen($desc)<3) return false;
        if(!isset(
self::$channels[$name]))
        {
            
self::$channels[$name]['name'] = $desc;
            
self::$channels[$name]['userlist'] = array();
            
self::$channels[$name]['founder'] = $this->name;
            
self::$channels[$name]['specialusers'] = array();
            return 
true;
        } else {return 
false;}
    }
    
    public function 
ir_canal($user$canal)
    {
        if(isset(
self::$channels[$canal]))
        {
            
// si estás banneado
            
if(isset(self::$channels[$canal]['specialusers'][$this->name]) && self::$channels[$canal]['specialusers'][$this->name] == 'B' && self::$channels[$canal]['founder'] != $this->name)
            { 
$this->channel null; return false; }
            
$this->channel $canal;
            
self::$channels[$canal]['userlist'][] = $user;
              if(
self::$channels[$canal]['founder'] == $this->name)
                  
$this->founder=true;
              else
                  
$this->founder=false;
              if(isset(
self::$channels[$canal]['specialusers'][$this->name]) && self::$channels[$canal]['specialusers'][$this->name] == 'M')  // si somos moderadores
                  
$this->moderator=true;
              else
                  
$this->moderator=false;
            return 
true;
        } else { return 
false; }
    }
    
    public function 
cambiar_pass($user$pass$pass2)
    {
        if(
strlen($pass2)<3) return false;
        if(isset(
self::$users[$user]) && self::$users[$user]==$pass)
        {
            
self::$users[$user]=$pass2;
            return 
true;
        } else {return 
false;}
    }
    
    public function 
get_accedio()
    {
      return 
$this->accedio;
    }
    
    public function 
get_name()
    {
      return 
$this->name;
    }
    
    public function 
get_channel()
    {
      return 
$this->channel;
    }
    
    public function 
set_channel($value)
    {
      
$this->channel $value;
    }
    
    public function 
get_founder()
    {
      return 
$this->founder;
    }
    
    public function 
get_moderator()
    {
      return 
$this->moderator;
    }

}

// Original Class
abstract class WebSocketServer {

protected $userClass 'WebSocketUser'// redefine this if you want a custom user class.  The custom user class should inherit from WebSocketUser.
protected $maxBufferSize;        
protected $master;
protected $sockets                              = array();
protected $users                                = array();
protected $interactive                          true;
protected $headerOriginRequired                 false;
protected $headerSecWebSocketProtocolRequired   false;
protected $headerSecWebSocketExtensionsRequired false;

function __construct($addr$port$userClass=null $bufferLength 2048) {
$this->maxBufferSize $bufferLength;
    if(!empty(
$userClass)) $this->userClass $userClass;
$this->master socket_create(AF_INETSOCK_STREAMSOL_TCP)  or die("Failed: socket_create()");
socket_set_option($this->masterSOL_SOCKETSO_REUSEADDR1) or die("Failed: socket_option()");
socket_bind($this->master$addr$port)                      or die("Failed: socket_bind()");
socket_listen($this->master,20)                               or die("Failed: socket_listen()");
$this->sockets[] = $this->master;
    
$this->stdout('###### WEBCHAT SERVER ###### v1.0');
$this->stdout("Server started\nListening on: $addr:$port\nMaster socket: ".$this->master);

while(true) {
if (empty($this->sockets)) {
$this->sockets[] = $master;
}
$read $this->sockets;
$write $except null;
@socket_select($read,$write,$except,null);
foreach ($read as $socket) {
if ($socket == $this->master) {
$client socket_accept($socket);
if ($client 0) {
$this->stderr("Failed: socket_accept()");
continue;
} else {
$this->connect($client);
}
} else {
$numBytes = @socket_recv($socket,$buffer,$this->maxBufferSize,0); // todo: if($numBytes === false) { error handling } elseif ($numBytes === 0) { remote client disconected }
if ($numBytes == 0) {
$this->disconnect($socket);
} else {
$user $this->getUserBySocket($socket);
if (!$user->handshake) {
$this->doHandshake($user,$buffer);
} else {
              if (
$message $this->deframe($buffer$user)) {
                
$this->process($userutf8_encode($message));
                if(
$user->hasSentClose) {
                  
$this->disconnect($user->socket);
                }
              } else {
do {
$numByte = @socket_recv($socket,$buffer,$this->maxBufferSize,MSG_PEEK);
if ($numByte 0) {
$numByte = @socket_recv($socket,$buffer,$this->maxBufferSize,0);
if ($message $this->deframe($buffer,$user)) {
$this->process($user,$message);
                      if(
$user->hasSentClose) {
                        
$this->disconnect($user->socket);
                      }
}
}
} while($numByte 0);
}
}
}
}
}
}
}
 
abstract protected function process($user,$message); // Calked immediately when the data is recieved. 
abstract protected function connected($user);        // Called after the handshake response is sent to the client.
abstract protected function closed($user);           // Called after the connection is closed.

protected function connecting($user) {
    
// Override to handle a connecting user, after the instance of the User is created, but before
    // the handshake has completed.
  
}
  
  protected function 
send($user,$message) {
//$this->stdout("> $message");
$message $this->frame($message,$user);
socket_write($user->socket,$message,strlen($message));
}

protected function connect($socket) {
$user = new $this->userClass(uniqid(),$socket);
array_push($this->users,$user);
array_push($this->sockets,$socket);
$this->connecting($user);
}

protected function disconnect($socket,$triggerClosed=true) {
$foundUser null;
$foundSocket null;
foreach ($this->users as $key => $user) {
if ($user->socket == $socket) {
$foundUser $key;
$disconnectedUser $user;
break;
}
}
if ($foundUser !== null) {
unset($this->users[$foundUser]);
$this->users array_values($this->users);
}
foreach ($this->sockets as $key => $sock) {
if ($sock == $socket) {
$foundSocket $key;
break;
}
}
if ($foundSocket !== null) {
unset($this->sockets[$foundSocket]);
$this->sockets array_values($this->sockets);
}
if ($triggerClosed) {
$this->closed($disconnectedUser);
}
}

protected function doHandshake($user$buffer) {
$magicGUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
$headers = array();
$lines explode("\n",$buffer);
foreach ($lines as $line) {
if (strpos($line,":") !== false) {
$header explode(":",$line,2);
$headers[strtolower(trim($header[0]))] = trim($header[1]);
} else if (stripos($line,"get ") !== false) {
preg_match("/GET (.*) HTTP/i"$buffer$reqResource);
$headers['get'] = trim($reqResource[1]);
}
}
if (isset($headers['get'])) {
$user->requestedResource $headers['get'];
} else {
// todo: fail the connection
$handshakeResponse "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
}
if (!isset($headers['host']) || !$this->checkHost($headers['host'])) {
$handshakeResponse "HTTP/1.1 400 Bad Request";
}
if (!isset($headers['upgrade']) || strtolower($headers['upgrade']) != 'websocket') {
$handshakeResponse "HTTP/1.1 400 Bad Request";

if (!isset($headers['connection']) || strpos(strtolower($headers['connection']), 'upgrade') === FALSE) {
$handshakeResponse "HTTP/1.1 400 Bad Request";
}
if (!isset($headers['sec-websocket-key'])) {
$handshakeResponse "HTTP/1.1 400 Bad Request";
} else {

}
if (!isset($headers['sec-websocket-version']) || strtolower($headers['sec-websocket-version']) != 13) {
$handshakeResponse "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocketVersion: 13";
}
if (($this->headerOriginRequired && !isset($headers['origin']) ) || ($this->headerOriginRequired && !$this->checkOrigin($headers['origin']))) {
$handshakeResponse "HTTP/1.1 403 Forbidden";
}
if (($this->headerSecWebSocketProtocolRequired && !isset($headers['sec-websocket-protocol'])) || ($this->headerSecWebSocketProtocolRequired && !$this->checkWebsocProtocol($header['sec-websocket-protocol']))) {
$handshakeResponse "HTTP/1.1 400 Bad Request";
}
if (($this->headerSecWebSocketExtensionsRequired && !isset($headers['sec-websocket-extensions'])) || ($this->headerSecWebSocketExtensionsRequired && !$this->checkWebsocExtensions($header['sec-websocket-extensions']))) {
$handshakeResponse "HTTP/1.1 400 Bad Request";
}

// Done verifying the _required_ headers and optionally required headers.

if (isset($handshakeResponse)) {
socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
$this->disconnect($user->socket);
return false;
}

$user->headers $headers;
$user->handshake $buffer;

$webSocketKeyHash sha1($headers['sec-websocket-key'] . $magicGUID);

$rawToken "";
for ($i 0$i 20$i++) {
$rawToken .= chr(hexdec(substr($webSocketKeyHash,$i*22)));
}
$handshakeToken base64_encode($rawToken) . "\r\n";

$subProtocol = (isset($headers['sec-websocket-protocol'])) ? $this->processProtocol($headers['sec-websocket-protocol']) : "";
$extensions = (isset($headers['sec-websocket-extensions'])) ? $this->processExtensions($headers['sec-websocket-extensions']) : "";

$handshakeResponse "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken$subProtocol$extensions\r\n";
socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
    
$this->connected($user);
}

protected function checkHost($hostName) {
return true// Override and return false if the host is not one that you would expect.
     // Ex: You only want to accept hosts from the my-domain.com domain,
 // but you receive a host from malicious-site.com instead.
}

protected function checkOrigin($origin) {
return true// Override and return false if the origin is not one that you would expect.
}

protected function checkWebsocProtocol($protocol) {
return true// Override and return false if a protocol is not found that you would expect.
}

protected function checkWebsocExtensions($extensions) {
return true// Override and return false if an extension is not found that you would expect.
}

protected function processProtocol($protocol) {
return ""// return either "Sec-WebSocket-Protocol: SelectedProtocolFromClientList\r\n" or return an empty string.  
   // The carriage return/newline combo must appear at the end of a non-empty string, and must not
   // appear at the beginning of the string nor in an otherwise empty string, or it will be considered part of 
   // the response body, which will trigger an error in the client as it will not be formatted correctly.
}

protected function processExtensions($extensions) {
return ""// return either "Sec-WebSocket-Extensions: SelectedExtensions\r\n" or return an empty string.
}

protected function getUserBySocket($socket) {
foreach ($this->users as $user) {
if ($user->socket == $socket) {
return $user;
}
}
return null;
}

  
// no la protegemos para que pueda ser reescrita si se cambia
  // el método de salida cuando se hereda la clase
private function stdout($message) {
if ($this->interactive) {
echo "$message\n";
}
}

  
// no la protegemos para que pueda ser reescrita si se cambia
  // el método de salida cuando se hereda la clase
private function stderr($message) {
if ($this->interactive) {
echo "$message\n";
}
}

protected function frame($message$user$messageType='text'$messageContinues=false) {
switch ($messageType) {
case 'continuous':
$b1 0;
break;
case 'text':
$b1 = ($user->sendingContinuous) ? 1;
break;
case 'binary':
$b1 = ($user->sendingContinuous) ? 2;
break;
case 'close':
$b1 8;
break;
case 'ping':
$b1 9;
break;
case 'pong':
$b1 10;
break;
}
if ($messageContinues) {
$user->sendingContinuous true;
} else {
$b1 += 128;
$user->sendingContinuous false;
}

$length strlen($message);
$lengthField "";
if ($length 126) {
$b2 $length;
} elseif ($length <= 65536) {
$b2 126;
$hexLength dechex($length);
//$this->stdout("Hex Length: $hexLength");
if (strlen($hexLength)%== 1) {
$hexLength '0' $hexLength;

$n strlen($hexLength) - 2;

for ($i $n$i >= 0$i=$i-2) {
$lengthField chr(hexdec(substr($hexLength$i2))) . $lengthField;
}
while (strlen($lengthField) < 2) {
$lengthField chr(0) . $lengthField;
}
} else {
$b2 127;
$hexLength dechex($length);
if (strlen($hexLength)%== 1) {
$hexLength '0' $hexLength;

$n strlen($hexLength) - 2;

for ($i $n$i >= 0$i=$i-2) {
$lengthField chr(hexdec(substr($hexLength$i2))) . $lengthField;
}
while (strlen($lengthField) < 8) {
$lengthField chr(0) . $lengthField;
}
}

return chr($b1) . chr($b2) . $lengthField $message;
}

protected function deframe($message$user) {
//echo $this->strtohex($message);
$headers $this->extractHeaders($message);
$pongReply false;
$willClose false;
switch($headers['opcode']) {
case 0:
case 1:
case 2:
break;
case 8:
// todo: close the connection
$user->hasSentClose true;
return "";
case 9:
$pongReply true;
case 10:
break;
default:
//$this->disconnect($user); // todo: fail connection
$willClose true;
break;
}

if ($user->handlingPartialPacket) {
$message $user->partialBuffer $message;
$user->handlingPartialPacket false;
return $this->deframe($message$user);
}

if ($this->checkRSVBits($headers,$user)) {
return false;
}

if ($willClose) {
// todo: fail the connection
return false;
}

$payload $user->partialMessage $this->extractPayload($message,$headers);

if ($pongReply) {
$reply $this->frame($payload,$user,'pong');
socket_write($user->socket,$reply,strlen($reply));
return false;
}
if (extension_loaded('mbstring')) {
if ($headers['length'] > mb_strlen($payload)) {
$user->handlingPartialPacket true;
$user->partialBuffer $message;
return false;
}
} else {
if ($headers['length'] > strlen($payload)) {
$user->handlingPartialPacket true;
$user->partialBuffer $message;
return false;
}
}

$payload $this->applyMask($headers,$payload);

if ($headers['fin']) {
$user->partialMessage "";
return $payload;
}
$user->partialMessage $payload;
return false;
}

protected function extractHeaders($message) {
$header = array('fin'     => $message[0] & chr(128),
'rsv1'    => $message[0] & chr(64),
'rsv2'    => $message[0] & chr(32),
'rsv3'    => $message[0] & chr(16),
'opcode'  => ord($message[0]) & 15,
'hasmask' => $message[1] & chr(128),
'length'  => 0,
'mask'    => "");
$header['length'] = (ord($message[1]) >= 128) ? ord($message[1]) - 128 ord($message[1]);

if ($header['length'] == 126) {
if ($header['hasmask']) {
$header['mask'] = $message[4] . $message[5] . $message[6] . $message[7];
}
$header['length'] = ord($message[2]) * 256 
  + ord($message[3]);
} elseif ($header['length'] == 127) {
if ($header['hasmask']) {
$header['mask'] = $message[10] . $message[11] . $message[12] . $message[13];
}
$header['length'] = ord($message[2]) * 65536 65536 65536 256 
  + ord($message[3]) * 65536 65536 65536
  + ord($message[4]) * 65536 65536 256
  + ord($message[5]) * 65536 65536
  + ord($message[6]) * 65536 256
  + ord($message[7]) * 65536 
  + ord($message[8]) * 256
  + ord($message[9]);
} elseif ($header['hasmask']) {
$header['mask'] = $message[2] . $message[3] . $message[4] . $message[5];
}
//echo $this->strtohex($message);
//$this->printHeaders($header);
return $header;
}

protected function extractPayload($message,$headers) {
$offset 2;
if ($headers['hasmask']) {
$offset += 4;
}
if ($headers['length'] > 65535) {
$offset += 8;
} elseif ($headers['length'] > 125) {
$offset += 2;
}
return substr($message,$offset);
}

protected function applyMask($headers,$payload) {
$effectiveMask "";
if ($headers['hasmask']) {
$mask $headers['mask'];
} else {
return $payload;
}

while (strlen($effectiveMask) < strlen($payload)) {
$effectiveMask .= $mask;
}
while (strlen($effectiveMask) > strlen($payload)) {
$effectiveMask substr($effectiveMask,0,-1);
}
return $effectiveMask $payload;
}
protected function checkRSVBits($headers,$user) { // override this method if you are using an extension where the RSV bits are used.
if (ord($headers['rsv1']) + ord($headers['rsv2']) + ord($headers['rsv3']) > 0) {
//$this->disconnect($user); // todo: fail connection
return true;
}
return false;
}

protected function strtohex($str) {
$strout "";
for ($i 0$i strlen($str); $i++) {
$strout .= (ord($str[$i])<16) ? "0" dechex(ord($str[$i])) : dechex(ord($str[$i]));
$strout .= " ";
if ($i%32 == 7) {
$strout .= ": ";
}
if ($i%32 == 15) {
$strout .= ": ";
}
if ($i%32 == 23) {
$strout .= ": ";
}
if ($i%32 == 31) {
$strout .= "\n";
}
}
return $strout "\n";
}

protected function printHeaders($headers) {
echo "Array\n(\n";
foreach ($headers as $key => $value) {
if ($key == 'length' || $key == 'opcode') {
echo "\t[$key] => $value\n\n";
} else {
echo "\t[$key] => ".$this->strtohex($value)."\n";

}

}
echo ")\n";
}
}

class 
phSocketPlus extends WebSocketServer implements iphSocketPlus
{
  use 
Property;
  
    public function 
__construct($addr$port=722$userClass='SocketPlusUser')
    {
      
parent::__construct($addr$port$userClass);
    }

  protected function 
process($user,$message// Calked immediately when the data is recieved.
  
{
      
// procesar
      #parent::send($user,$mensaje);
      // obtenemos el mensaje y vemos que comando es
      
$command null;
      for(
$i=0$i<3$i++)
      {
        
$command .= $message[$i];
      }
      
$params null;
      for(
$i=4$i<strlen($message); $i++)
      {
        
$params .= $message[$i];
      }
      switch(
$command)
      {
          case 
'AUT':
            if(!
$user->accedio)
            {
              
$this->HEout('AUTentificate command');
              if(
$this->autenticate($user$params))
              {
                  
$this->MEout('Welcome '.$user->name.'...');
                  
parent::send($user,'#SERVER: Welcome '.htmlentities($user->name).'...');
              } else { 
parent::send($user,'#SERVER: Access Deneid...'); }
            } else { 
parent::send($user,'#SERVER: @'.$user->name); }
          break;
          case 
'NEW':
            if(!
$user->accedio)
            {
              
$this->HEout('New Account command - '.$params);
              if(
$this->registrate($user$params))
              {
                  
$this->MEout('New Account '.htmlentities($params).'...');
                  
parent::send($user,'#SERVER: new account create success...');
              } else { 
parent::send($user,'#SERVER: Isn\'t possible register your account...'); }
            } else { 
parent::send($user,'#SERVER: @'.htmlentities($user->name)); }
          break;
          case 
'CHN':
            if(
$user->accedio)
            {
              
$this->HEout('Join Channel command');
              
// si ya tenía canal
              
if($user->channel!=null$this->killchannel($user);
              if(
$this->ir_canal($user$params))
              {
                  
$this->MEout('Join to channel '.$params.'...');
                  
$this->meunicanal($user);
                  
parent::send($user,'#SERVER: Welcome to channel: '.SocketPlusUser::$channels[$params]['name']);
              } else { 
parent::send($user,'#SERVER: the channel '.htmlentities($params).' not exist...'); $user->channel null;}
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'PWD':
            if(
$user->accedio)
            {
              
$this->HEout('Change password command');
              if(
$this->cambiar_pass($user$params))
              {
                  
$this->MEout('Change Password '.$params.'...');
                  
parent::send($user,'#SERVER: Change Password Success');
              } else { 
parent::send($user,'#SERVER: isn\'t possible change password...'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'ULT':
            if(
$user->accedio)
            {
              
$this->HEout('User List command');
              if(
$user->channel!=null)
              {
                
parent::send($user,'#SERVER: Users in this channel:');
                
$this->obtener_usuarios($user);
                
$this->MEout('User List...');
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'NEC':
              if(
$user->accedio)
              {
                
$this->HEout('New Chanel command');
                if(
$this->crear_canal($user$params))
                {
                    
parent::send($user,'#SERVER: Channel Create Success');
                    
$this->MEout('New Channel Create Success...');
                } else { 
parent::send($user,'#SERVER: Channel isn\'t create, type HLP for help'); }
                
              }
              else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'MOD':
            if(
$user->accedio)
            {
              
$this->HEout('Add Moderator command');
              if(
$user->channel!=null)
              {
                if(
$user->founder==true)
                {
                    
$this->addmod($user$params);
                    
parent::send($user,'#SERVER: Add Moderator :)');
                    
$this->MEout('Add Moderator To Channel...');
                } else { 
parent::send($user,'#SERVER: You aren\'t founder, type HLP for help'); }
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'USR':
            if(
$user->accedio)
            {
              
$this->HEout('Delete Moderator command');
              if(
$user->channel!=null)
              {
                if(
$user->founder==true)
                {
                    
$this->quitmod($user$params);
                    
parent::send($user,'#SERVER: Delete Moderator');
                    
$this->MEout('Delete Moderator of the Channel...');
                } else { 
parent::send($user,'#SERVER: You aren\'t founder, type HLP for help'); }
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'BAN':
            if(
$user->accedio)
            {
              
$this->HEout('REQUEST BAN USER command');
              if(
$user->channel!=null)
              {
                if(
$user->founder==true || $user->moderator==true)
                {
                    
$this->ban($user$params);
                    
parent::send($user,'#SERVER: Ban user success');
                    
$this->MEout('Ban Aproved...');
                } else { 
parent::send($user,'#SERVER: You aren\'t Founder or Moderator, type HLP for help'); }
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'KIK':
            if(
$user->accedio)
            {
              
$this->HEout('REQUEST KIK USER command');
              if(
$user->channel!=null)
              {
                if(
$user->founder==true || $user->moderator==true)
                {
                    
$this->kik($user$params);
                    
parent::send($user,'#SERVER: Kik user success');
                    
$this->MEout('Kik Aproved...');
                } else { 
parent::send($user,'#SERVER: You aren\'t Founder or Moderator, type HLP for help'); }
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'DEL':
            if(
$user->accedio)
            {
              
$this->HEout('REQUEST DELETE CHANEL command');
              if(
$user->channel!=null)
              {
                if(
$user->founder==true || $user->moderator==true)
                {
                    
//$this->channeldelete($user, $params);  <---- Falta Hacer
                    
parent::send($user,'#SERVER: Delete chanel deneid by SERVER');
                    
$this->MEout('Delete channel Deneid...');
                } else { 
parent::send($user,'#SERVER: You aren\'t Founder or Moderator, type HLP for help'); }
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'GRD':
            if(
$user->accedio)
            {
              
$this->HEout('REQUEST GRADE USER command');
              if(
$user->channel!=null)
              {
                    
parent::send($user,'#SERVER: '.$this->getGrade($user));
                    
$this->MEout('Grade sent');
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
          case 
'HLP':
               
$this->HEout('help command');
               
$this->MEout('Show Help...');
               
parent::send($user,'#SERVER: Help Command');
               
parent::send($user,'*Acces your account use: AUT user@pass');
               
parent::send($user,'*View Help use: HLP');
               
parent::send($user,'*Register an account use: NEW user@pass');
               
parent::send($user,'*Change your password use: PWD user@oldpass@newpass');
               
parent::send($user,'*Access to channel: CHN ChannelName');  
               
parent::send($user,'*List of users in channel: ULT'); 
               
parent::send($user,'*New channel: NEC ChannelName@Channel Welcome');
               
parent::send($user,'*Kik User: KIK user');
               
parent::send($user,'*Ban user: BAN user');
               
parent::send($user,'*Moderator Add: MOD user');
               
parent::send($user,'*Moderator Delete: USR user');
               
parent::send($user,'*Get Grade (is mod, is founder or is user): GRD');
               
parent::send($user,'*Destroy Channel: DEL mypassword');            //TODO
               
parent::send($user,'*[M]userName: hello <-- is a moderator');
               
parent::send($user,'*[F]userName: hello <-- is a founder');
          break;
          default:
            if(
$user->accedio)
            {
              if(
$user->channel!=null)
              {
                
$this->HEout('MSG command');
                
$this->resend($user$message);
                
$this->MEout('New Message... Resending to terminals');
              }
              else { 
parent::send($user,'#SERVER: Join Channel is required, type HLP for help'); }
            } else { 
parent::send($user,'#SERVER: Authentication is required, type HLP for help'); }
          break;
      }
  }
  
  private function 
autenticate($user$params)
  {
    
$user_init strpos($params,'@');
    
$usr null;
    if(
$user_init===false) return false
    for(
$i 0$i<$user_init$i++)
      
$usr.=$params[$i];
    
$pss null;
    for(
$i $user_init+1$i<strlen($params); $i++)
      
$pss.=$params[$i];
    return 
$user->acceder($usr$pss);
  }
  
  private function 
crear_canal($user$params)
  {
    
$user_init strpos($params,'@');
    
$name null;
    if(
$user_init===false) return false
    for(
$i 0$i<$user_init$i++)
      
$name.=$params[$i];
    
$name trim($name);
    
$desc null;
    for(
$i $user_init+1$i<strlen($params); $i++)
      
$desc.=$params[$i];
    return 
$user->crear_canal($name$desc);
  }
  
  private function 
getGrade($user)
  {
      if(
$user->founder)
          return 
'You Are a Founder of the channel';
      elseif(
$user->moderator)
          return 
'You Are a Moderator of the channel';
      else
          return 
'You Are a User in the channel';
  }
  
  private function 
cambiar_pass($user$params)
  {
    
$user_init strpos($params,'@');
    
$usr null;
    if(
$user_init===false) return false
    for(
$i 0$i<$user_init$i++)
      
$usr.=$params[$i];
    
$passs null;
    for(
$i $user_init+1$i<strlen($params); $i++)
      
$passs.=$params[$i];
    
$pass_init strpos($passs,'@');
    if(
$pass_init===false) return false;
    
$pss1 null;
    for(
$i 0$i<$pass_init$i++)
      
$pss1.=$passs[$i];
    
$pss2 null;
    for(
$i $pass_init+1$i<strlen($passs); $i++)
      
$pss2.=$passs[$i];
    return 
$user->cambiar_pass($usr$pss1$pss2);
  }
  
  private function 
registrate($user$params)
  {
    
$user_init strpos($params,'@');
    
$usr null;
    if(
$user_init===false) return false
    for(
$i 0$i<$user_init$i++)
      
$usr.=$params[$i];
    
$pss null;
    for(
$i $user_init+1$i<strlen($params); $i++)
      
$pss.=$params[$i];
    return 
$user->registrarse($usr$pss);
  }
  
  private function 
obtener_usuarios($user)
  {
      
      
$userlist SocketPlusUser::$channels[$user->channel]['userlist'];
      for(
$i=0$i<count($userlist); $i++)
      {
          
$rango '@';
          if(
$userlist[$i]->founder == true$rango '[F]';
          if(
$user->moderator == true$rango '[M]';
          
parent::send($user$rango.$userlist[$i]->name);
      }
  }
  
  private function 
resend($user$message)
  {
      
$rango '@';
      if(
$user->founder == true$rango '[F]';
      if(
$user->moderator == true$rango '[M]';
      
$userlist SocketPlusUser::$channels[$user->channel]['userlist'];
      for(
$i=0$i<count($userlist); $i++)
      {
          if(
$userlist[$i]->name != $user->name// no nos enviamos a nosotros mismos
              
parent::send($userlist[$i], $rango.$user->name.': '.$message);
      }
  }
  
  private function 
addmod($me$user)
  {
    
SocketPlusUser::$channels[$me->channel]['specialusers'][$user]='M';
  }
  
  private function 
quitmod($me$user)
  {
    unset(
SocketPlusUser::$channels[$me->channel]['specialusers'][$user]);
  }
  
  private function 
ban($me$user)
  {
    
SocketPlusUser::$channels[$me->channel]['specialusers'][$user]='B';
  }
  
  private function 
kik($user$objetivo// el user se fue del canal
  
{
      
$userlist SocketPlusUser::$channels[$user->channel]['userlist'];
      for(
$i=0$i<count($userlist); $i++)
      {
          if(
$userlist[$i]->founder != true)
            {
              
$userlist[$i]->channel null;
              unset(
SocketPlusUser::$channels[$user->channel]['userlist'][$i]);
              
SocketPlusUser::$channels[$user->channel]['userlist'] = array_values(SocketPlusUser::$channels[$user->channel]['userlist']);
              
parent::send($userlist[$i], '#SERVER You Forced Disconnected from the channel');
            }
      }
  }
  
  private function 
killchannel($user// el user se fue del canal
  
{
      
$rango '@';
      if(
$user->founder == true$rango '[F]';
      if(
$user->moderator == true$rango '[M]';
      
$userlist SocketPlusUser::$channels[$user->channel]['userlist'];
      for(
$i=0$i<count($userlist); $i++)
      {
          if(
$userlist[$i]->name != $user->name// no nos enviamos a nosotros mismos
              
parent::send($userlist[$i], '***Lost Connection: '.$rango.$user->name);
          else
           {
                unset(
SocketPlusUser::$channels[$user->channel]['userlist'][$i]); //lo quitamos de la lista de usuarios
                
SocketPlusUser::$channels[$user->channel]['userlist'] = array_values(SocketPlusUser::$channels[$user->channel]['userlist']);
           }
      }
  }
  
  private function 
meunicanal($user// el user se fue del canal
  
{
      
$rango '@';
      if(
$user->founder == true$rango '[F]';
      if(
$user->moderator == true$rango '[M]';
      
$userlist SocketPlusUser::$channels[$user->channel]['userlist'];
      for(
$i=0$i<count($userlist); $i++)
      {
          if(
$userlist[$i]->name != $user->name// no nos enviamos a nosotros mismos
              
parent::send($userlist[$i], '***JOIN A CHANEL: '.$rango.$user->name);
      }
  }
  
  private function 
ir_canal($user$params)
  {
    return 
$user->ir_canal($user$params);
  }
  
  private function 
stdout($message)
  {
      echo 
'-----<>'.$message."\r\n";
  }
  
  private function 
MEout($message)
  {
      echo 
'<<-----'.$message."\r\n";
  }
  
  private function 
HEout($message)
  {
      echo 
'----->>'.$message."\r\n";
  }
  
  private function 
stderr($message)
  {
      echo 
'-----**'.$message."\r\n";
  }
  
protected function connected($user)        // Called after the handshake response is sent to the client.
  
{
      echo 
'-----<> Connected Success #'.$user->id."\r\n";
  }
  
protected function closed($user)           // Called after the connection is closed.
  
{
      
// borrar de la lista donde estaba dicho user
      
$this->killchannel($user);
      
// nos vimos!
      
echo '-----<> CLOSE: #'.$user->id."\r\n";
  }
  
}

// definimos los usuarios:
SocketPlusUser::$users = array('alex' => '1234',
                               
'prueba' => '1234');

SocketPlusUser::$channels[1]['name'] = 'Canal de ejemplo';
SocketPlusUser::$channels[1]['userlist'] = null;

SocketPlusUser::$channels['alex']['name'] = 'El Canal de alex';
SocketPlusUser::$channels['alex']['userlist'] = null;

// iniciamos el servidor
$socket = new phSocketPlus('localhost','778');


estube a punto de implementar hilos (threads) pero, se me acabó el tiempo asique lo publico así.

este código permite crear varias salas, poner moderadores, et
Título: Re:CHAT WebSocket y php, con PHPCLI y mis disculpas jejeje
Publicado por: Alex en Octubre 07, 2013, 06:27:51 PM
sigo acá abajo, porque no me deja poner más nada en el comentario de arriba jajajjaja

Como decía, permite crear salas, poner moderadores, es parecido a un IRC.
Aunque no me baso en ningún protocolo como xmpp o IRC, por lo que no aconcejo el uso de éste código.

es simplemente demostrativo.

dejo el HTML que conecta con el servidor de arriba.

Código (HTML5) [Seleccionar]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Abrir un WebSocket</title>
</head>
<body>

<h1>Chat de prueba</h1>

<script type="text/javascript">


        function escribir(texto){
                valor = document.getElementById("caja").value;
                document.getElementById("caja").value = texto + "\n" + valor;
        }       

        var mysocket = new WebSocket("ws://localhost:778");

        mysocket.onopen = function (evt){
                escribir("Conectado al chat...");
               
        };

        mysocket.onmessage = function (evt){
                escribir(evt.data);
               
        };

        mysocket.onclose = function (evt){
                 escribir("Desconectado del chat...");
        };

        mysocket.onerror = function (evt) {
                escribir("ERROR: " + evt.data);
        }

        function enviar(texto) {
                mysocket.send(texto);
                escribir("Yo: " + texto);
        }

        function desconectar(){         
                mysocket.close();
        }

       
</script>


<textarea id="caja" cols="100" rows="20"></textarea><br/>
<input id="mensaje" type="text" size="105"></input>
<button onClick="enviar(document.getElementById('mensaje').value);">Enviar</button>


<br><br>
<hr>


</body>
</html>


no es de mi autoría pero lo googlee asique no se de quien es jajajaj, si alguien encuentra fuente por favor la agrega.

saludos!