Sujet : Hoa_Websocket et démo

Hey smile,

Hoa_Websocket se trouve dans le trunk.
On trouve une vidéo de démonstration : http://j.mp/websocket_w (webm) et http://j.mp/websocket_m (mov).
Je vous laisse apprécier smile.

« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Re : Hoa_Websocket et démo

Yep,

C'est sympa, ça éviterais de se poser trop de questions pour faire du pull en js/curl etc...
Quelques questions:
-Je me doute que ça reste compatible seulement avec les dernières versions de dev de firefox et chrome?
-Si j'ai bien pigé, il y a un "processus" php qui tourne dans le vide pour le serveur? J'ai toujours entendu que php n'était pas fait pour tourner comme ça? qu'en est-il des perfs à ce niveau là?
-On pourrait avoir un aperçu du code js utilisé pour la démo? (ou carrément la démo en ligne smile -limité à 5 utilisateurs si tu ne veux pas plier ton server wink -

Chouette en tout cas!

3

Re : Hoa_Websocket et démo

Compatible Firefox, Chrome, Opera et Safari. Que demande le peuple ?
Un processus, oui bien sûr hein wink. Tu as une boucle active et une boucle passive :

while(true)
    foreach($server->select() as $node)
        … // et on appelle $server->compute(…) à ce moment là.

La méthode select() ne fait qu'appeler la fonction select() des sockets en C. C'est là que tu as une boucle passive, c'est du bas-niveau. Sans expliquer comment fonctionne select(), je peux te rassurer en te disant que ça ne mange pas 100% du processus smile. Je viens de faire un petit test très rapide, on ne dépasse pas 1% du processus à chaque échange de message, et pendant qu'il n'y a pas d'échange, c'est 0%. Je pense que ça devrait répondre à ta question.

Oui la démo est en ligne et donc le code Javascript aussi : http://lab.hoa-project.net/Websocket/. Voici le code du serveur :

<?php

set_include_path('/var/hoa/Core/' .
                 PATH_SEPARATOR .
                 get_include_path());

require_once 'Core.php';
import('Socket.Internet.DomainName');
import('Websocket.Server');


class Chat extends Hoa_Websocket_Server {

    protected function compute ( $sourceNode, $message ) {

        $id    = $sourceNode->getId();
        $nodes = $this->getNodes();
        $count = count($nodes);

        foreach($nodes as $i => $node)
            if(true === $node->isFirstMessage()) {

                $this->send($node, $count . ';' . $message);
                $node->setFirst(false);
            }
            elseif($node->getId() != $id)
                $this->send($node, $count . ';' . $message);

        return;
    }
}

$ip = new Hoa_Socket_Internet_DomainName(
    'lab.hoa-project.net', 8889, 'tcp'
);
$ws = new Chat($ip);

5 utilisateurs, pas vraiment, on peut aller à plus que ça. Paul Rouget a fait un test de WebSocket sur un serveur basique et il est monté à 350/360 connexions simulatannées. Et ce n'était pas optimisé pour.

« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Re : Hoa_Websocket et démo

Merci pour les précisions!
(par contre j'arrive pas à faire marcher ça sous chrome? (dernière version 6.*) et j'ai la flemme de télécharger une béta de firefox hmm

5

Re : Hoa_Websocket et démo

Oui, j'ai tué le serveur. Je fais des tests smile.

« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

6

Re : Hoa_Websocket et démo

Venez sur http://lab.hoa-project.net/Websocket/ pour tester smile.

« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

7

Re : Hoa_Websocket et démo

J'aime bien ^^

Le code c'est comme le paic citron, quand il y en a plus... il y en a encore !

8

Re : Hoa_Websocket et démo

Je ne sais pas si c'est utile, mais voici ce que j'ai extrait pour mieux comprendre le fonctionnement de tout le bazar.
C'est un standalone du serveur sans Hoa. Inspiration pour le ->select() et recopiage pour le ->handshake(). wink

<?php

class Server {
    protected $clients = array();
    protected $master  = array();

    function __construct($uri = 'tcp://0.0.0.0:8000') {
        $this->master = stream_socket_server($uri, $errno, $errstr);
        if (!$this->master) {
            echo "$errstr ($errno)<br />\n";
        } else {
            while (true) {
                foreach($this->select() as $client) {
                    $buffer = $client->read(2048);
                    if (!$client->handshake($buffer)) {
                        continue;
                    }
                    $this->compute($client, substr($buffer, 1, -1));
                }
            }
            fclose($this->master);
        }
    }

    function compute($clientFrom, $message) {
        echo "Received $message from $clientFrom->socket\n";
        echo count($this->clients)." connected\n";
        foreach($this->clients as $clientTo) {
            echo "Dispatching to $clientTo->socket\n";
            $clientTo->send(chr(0).$message.chr(255));
        }
    }

    function select() {
        $read    = array_map(function($client) {
            return $client->socket;
        }, $this->clients);
        $read[] = $this->master;
        $write  = null;
        $except = null;
        stream_select($read, $write, $except, 30);

        $found = array();
        foreach ($read as $current) {
            if ($current == $this->master) {
                $socket = stream_socket_accept($current);
                $client = new Client($socket, $this);
                $this->clients[(string) $socket] = $client;
                $found[] = $client;
            } else {
                $found[] = $this->clients[(string) $current];
            }
        }
        return $found;
    }

    function onClientClose($client) {
        echo "Client $client->socket closed\n";
        unset($this->clients[(string) $client->socket]);
    }

    function close() {
        foreach ($this->clients as $client) {
            $client->close();
        }
        @fclose($this->master);
    }

    function  __destruct() {
        $this->close();
    }
}

class Client {

    public    $socket;
    protected $server;
    protected $handshake = false;

    function __construct($socket, $server) {
        $this->socket = $socket;
        $this->server = $server;
    }

    function send($message) {
        echo "Sending $message to $this->socket\n";
        return stream_socket_sendto($this->socket, $message);
    }

    function read($length) {
        $message = @stream_socket_recvfrom($this->socket, $length);
        if ($message == '') {
            $this->close();
        }
        return $message;
    }

    function close() {
        $this->server->onClientClose($this);
        @fclose($this->socket);
    }

    public function handshake($buffer) {

        if ($this->handshake) {
            return true;
        }

        $x = explode("\r\n", $buffer);
        $h = array();

        for($i = 1, $m = count($x) - 3; $i <= $m; ++$i)
            $h[strtolower(substr($x[$i], 0, strpos($x[$i], ':')))] =
                trim(substr($x[$i], strpos($x[$i], ':') + 2));

        if(0 !== preg_match('#GET (.*) HTTP#', $buffer, $match))
            $h['resource'] = $match[1];

        $key1      = $h['sec-websocket-key1'];
        $key2      = $h['sec-websocket-key2'];
        $key3      = $x[count($x) - 1];
        $keynumb1  = (int) preg_replace('#[^0-9]#', '', $key1);
        $keynumb2  = (int) preg_replace('#[^0-9]#', '', $key2);

        $spaces1   = substr_count($key1, ' ');
        $spaces2   = substr_count($key2, ' ');

        $part1     = pack('N', $keynumb1 / $spaces1);
        $part2     = pack('N', $keynumb2 / $spaces2);
        $challenge = $part1 . $part2 . $key3;
        $response  = md5($challenge, true);

        $this->send(
            'HTTP/1.1 101 WebSocket Protocol Handshake' . "\r\n" .
            'Upgrade: WebSocket' . "\r\n" .
            'Connection: Upgrade' . "\r\n" .
            'Sec-WebSocket-Origin: ' . $h['origin'] . "\r\n" .
            'Sec-WebSocket-Location: ws://' . $h['host'] .
            $h['resource'] . "\r\n" .
            "\r\n" .
            $response . "\r\n"
        );

        $this->handshake = true;
        return false;
    }

    function  __destruct() {
        $this->close();
    }
}

new Server('tcp://0.0.0.0:8080');

9

Re : Hoa_Websocket et démo

C'est l'idée oui. Mais sinon, tu prends Hoa\Stream, Hoa\Socket et Hoa\Websocket et le tour est joué. Ça fait toujours un standalone aussi, et tu as toute la puissance des flux. Mais tu as pris l'essentiel il me semble, je n'ai pas vu de chose qu'il manquait.

Euh sauf un truc peut-être. select() retourne ce qui s'est ouvert … hmm, je pense qu'il y aura des cas où tu auras du « lag », une sorte de décalage voire des oublis de connexions. Il faudrait réfléchir à ça.

« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

10

Re : Hoa_Websocket et démo

Hum. Je viens de revérifier avec ton code, et effectivement, ton select() ne retourne pas directement les nouvelles connexions. Il suffirait juste de supprimer la ligne

$found[] = $client;

, non ?

Le client est ajouté, donc il serait traité au prochain select(). Tout simplement. ^^