CHAT WebSocket y php, con PHPCLI y mis disculpas jejeje

Iniciado por alexander1712, Octubre 07, 2013, 05:50:40 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Octubre 07, 2013, 05:50:40 PM Ultima modificación: Octubre 07, 2013, 06:26:15 PM por alexmanycol
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 No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

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
<?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)<3 || 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)<3 || 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_INET, SOCK_STREAM, SOL_TCP)  or die("Failed: socket_create()");
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) 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($user, utf8_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*2, 2)));
}
$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) ? 0 : 1;
break;
case 'binary':
$b1 = ($user->sendingContinuous) ? 0 : 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)%2 == 1) {
$hexLength = '0' . $hexLength;
}
$n = strlen($hexLength) - 2;

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

for ($i = $n; $i >= 0; $i=$i-2) {
$lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $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

Octubre 07, 2013, 06:27:51 PM #1 Ultima modificación: Marzo 27, 2014, 05:25:09 PM por Expermicid
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
<!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!